package com.storycove.service;

import com.storycove.entity.Library;
import com.storycove.dto.LibraryDto;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class LibraryService implements ApplicationContextAware {
    private static final Logger logger = LoggerFactory.getLogger(LibraryService.class);
    
    @Value("${spring.datasource.url}")
    private String baseDbUrl;

    @Value("${spring.datasource.username}")
    private String dbUsername;

    @Value("${spring.datasource.password}")
    private String dbPassword;

    @Value("${storycove.auth.password}")
    private String appPassword;
    
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    private final Map<String, Library> libraries = new ConcurrentHashMap<>();
    
    // Spring ApplicationContext for accessing other services without circular dependencies
    private ApplicationContext applicationContext;
    
    // Current active resources
    private volatile String currentLibraryId;
    
    // Security: Track if user has explicitly authenticated in this session
    private volatile boolean explicitlyAuthenticated = false;
    
    private static final String LIBRARIES_CONFIG_PATH = "/app/config/libraries.json";
    private static final Path libraryConfigDir = Paths.get("/app/config");
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
    
    @PostConstruct
    public void initialize() {
        loadLibrariesFromFile();
        
        // If no libraries exist, create a default one
        if (libraries.isEmpty()) {
            createDefaultLibrary();
        }
        
        // Security: Do NOT automatically switch to any library on startup
        // Users must authenticate before accessing any library
        explicitlyAuthenticated = false;
        currentLibraryId = null;
        
        if (!libraries.isEmpty()) {
            logger.info("Loaded {} libraries. Authentication required to access any library.", libraries.size());
        } else {
            logger.info("No libraries found. A default library will be created on first authentication.");
        }
        
        logger.info("Security: Application startup completed. All users must re-authenticate.");
    }
    
    @PreDestroy
    public void cleanup() {
        currentLibraryId = null;
        explicitlyAuthenticated = false;
    }
    
    /**
     * Clear authentication state (for logout)
     */
    public void clearAuthentication() {
        explicitlyAuthenticated = false;
        currentLibraryId = null;
        logger.info("Authentication cleared - user must re-authenticate to access libraries");
    }
    
    
    public String authenticateAndGetLibrary(String password) {
        for (Library library : libraries.values()) {
            if (passwordEncoder.matches(password, library.getPasswordHash())) {
                explicitlyAuthenticated = true;
                logger.info("User explicitly authenticated for library: {}", library.getId());
                return library.getId();
            }
        }

        // APP_PASSWORD acts as a master override for recovery access.
        // Grants access to the "main" library, or the first available one.
        if (appPassword != null && !appPassword.isBlank() && appPassword.equals(password)) {
            String libraryId = libraries.containsKey("main")
                    ? "main"
                    : libraries.keySet().iterator().next();
            explicitlyAuthenticated = true;
            logger.warn("Master APP_PASSWORD used for recovery access to library: {}", libraryId);
            return libraryId;
        }

        return null;
    }
    
    /**
     * Switch to library after authentication with forced reindexing
     * This ensures Solr is always up-to-date after login
     */
    public synchronized void switchToLibraryAfterAuthentication(String libraryId) throws Exception {
        logger.info("Switching to library after authentication: {} (forcing reindex)", libraryId);
        switchToLibrary(libraryId, true);
    }
    
    public synchronized void switchToLibrary(String libraryId) throws Exception {
        switchToLibrary(libraryId, false);
    }
    
    public synchronized void switchToLibrary(String libraryId, boolean forceReindex) throws Exception {
        // Security: Only allow library switching after explicit authentication
        if (!explicitlyAuthenticated) {
            throw new IllegalStateException("Library switching requires explicit authentication. Please log in first.");
        }
        
        if (libraryId.equals(currentLibraryId) && !forceReindex) {
            return; // Already active and no forced reindex requested
        }
        
        Library library = libraries.get(libraryId);
        if (library == null) {
            throw new IllegalArgumentException("Library not found: " + libraryId);
        }
        
        String previousLibraryId = currentLibraryId;
        
        if (libraryId.equals(currentLibraryId) && forceReindex) {
            logger.debug("Forcing reindex for current library: {} ({})", library.getName(), libraryId);
        } else {
            logger.debug("Switching to library: {} ({})", library.getName(), libraryId);
        }
        
        // Close current resources
        closeCurrentResources();
        
        // Set new active library (datasource routing handled by SmartRoutingDataSource)
        currentLibraryId = libraryId;
        // Solr indexes are global - no per-library initialization needed
        logger.debug("Library switched to Solr mode for library: {}", libraryId);
        
        logger.info("Successfully switched to library: {}", library.getName());
        
        // Perform complete reindex AFTER library switch is fully complete
        // This ensures database routing is properly established
        if (forceReindex || !libraryId.equals(previousLibraryId)) {
            logger.debug("Starting post-switch Solr reindex for library: {}", libraryId);

            // Run reindex asynchronously to avoid blocking authentication response
            // and allow time for database routing to fully stabilize
            String finalLibraryId = libraryId;
            new Thread(() -> {
                try {
                    // Give routing time to stabilize
                    Thread.sleep(500);
                    logger.debug("Starting async Solr reindex for library: {}", finalLibraryId);

                    SearchServiceAdapter searchService = applicationContext.getBean(SearchServiceAdapter.class);
                    // Get all stories and authors for reindexing
                    StoryService storyService = applicationContext.getBean(StoryService.class);
                    AuthorService authorService = applicationContext.getBean(AuthorService.class);

                    var allStories = storyService.findAllWithAssociations();
                    var allAuthors = authorService.findAllWithStories();

                    searchService.bulkIndexStories(allStories);
                    searchService.bulkIndexAuthors(allAuthors);

                    logger.info("Completed async Solr reindexing for library: {} ({} stories, {} authors)",
                        finalLibraryId, allStories.size(), allAuthors.size());
                } catch (Exception e) {
                    logger.warn("Failed to async reindex Solr for library {}: {}", finalLibraryId, e.getMessage());
                }
            }, "SolrReindex-" + libraryId).start();
        }
    }
    
    public DataSource getCurrentDataSource() {
        if (currentLibraryId == null) {
            throw new IllegalStateException("No active library - please authenticate first");
        }
        // Return the Spring-managed primary datasource which handles routing automatically
        try {
            return applicationContext.getBean("dataSource", DataSource.class);
        } catch (Exception e) {
            throw new IllegalStateException("Failed to get routing datasource", e);
        }
    }
    
    
    public String getCurrentLibraryId() {
        return currentLibraryId;
    }
    
    public Library getCurrentLibrary() {
        if (currentLibraryId == null) {
            return null;
        }
        return libraries.get(currentLibraryId);
    }
    
    public List<LibraryDto> getAllLibraries() {
        List<LibraryDto> result = new ArrayList<>();
        for (Library library : libraries.values()) {
            boolean isActive = library.getId().equals(currentLibraryId);
            result.add(new LibraryDto(
                library.getId(),
                library.getName(),
                library.getDescription(),
                isActive,
                library.isInitialized()
            ));
        }
        return result;
    }
    
    public LibraryDto getLibraryById(String libraryId) {
        Library library = libraries.get(libraryId);
        if (library != null) {
            boolean isActive = library.getId().equals(currentLibraryId);
            return new LibraryDto(
                library.getId(),
                library.getName(),
                library.getDescription(),
                isActive,
                library.isInitialized()
            );
        }
        return null;
    }
    
    public String getCurrentImagePath() {
        Library current = getCurrentLibrary();
        return current != null ? current.getImagePath() : "/images/default";
    }
    
    public String getDbNameForLibrary(String libraryId) {
        Library library = libraries.get(libraryId);
        return library != null ? library.getDbName() : null;
    }

    public String getImagePathForLibrary(String libraryId) {
        if (libraryId == null) {
            return "/images/default";
        }
        
        Library library = libraries.get(libraryId);
        return library != null ? library.getImagePath() : "/images/default";
    }
    
    public boolean changeLibraryPassword(String libraryId, String currentPassword, String newPassword) {
        Library library = libraries.get(libraryId);
        if (library == null) {
            return false;
        }
        
        // Verify current password
        if (!passwordEncoder.matches(currentPassword, library.getPasswordHash())) {
            return false;
        }
        
        // Update password
        library.setPasswordHash(passwordEncoder.encode(newPassword));
        saveLibrariesToFile();
        
        logger.info("Password changed for library: {}", library.getName());
        return true;
    }
    
    public Library createNewLibrary(String name, String description, String password) {
        // Generate unique ID
        String id = name.toLowerCase().replaceAll("[^a-z0-9]", "");
        int counter = 1;
        String originalId = id;
        while (libraries.containsKey(id)) {
            id = originalId + counter++;
        }
        
        Library newLibrary = new Library(
            id,
            name,
            description,
            passwordEncoder.encode(password),
            "storycove_" + id
        );
        
        try {
            // Test database creation by creating a connection
            DataSource testDs = createDataSource(newLibrary.getDbName());
            testDs.getConnection().close(); // This will create the database and schema if it doesn't exist
            
            // Initialize library resources (image directories)
            initializeNewLibraryResources(id);
            
            newLibrary.setInitialized(true);
            logger.info("Database and resources created for library: {}", newLibrary.getDbName());
        } catch (Exception e) {
            logger.warn("Database/resource creation failed for library {}: {}", id, e.getMessage());
            // Continue anyway - resources will be created when needed
        }
        
        libraries.put(id, newLibrary);
        saveLibrariesToFile();
        
        logger.info("Created new library: {} ({})", name, id);
        return newLibrary;
    }
    
    private void loadLibrariesFromFile() {
        try {
            File configFile = new File(LIBRARIES_CONFIG_PATH);
            if (configFile.exists()) {
                String content = Files.readString(Paths.get(LIBRARIES_CONFIG_PATH));
                Map<String, Object> config = objectMapper.readValue(content, new TypeReference<Map<String, Object>>() {});
                
                @SuppressWarnings("unchecked")
                Map<String, Map<String, Object>> librariesData = (Map<String, Map<String, Object>>) config.get("libraries");
                
                for (Map.Entry<String, Map<String, Object>> entry : librariesData.entrySet()) {
                    String id = entry.getKey();
                    Map<String, Object> data = entry.getValue();
                    
                    Library library = new Library();
                    library.setId(id);
                    library.setName((String) data.get("name"));
                    library.setDescription((String) data.get("description"));
                    library.setPasswordHash((String) data.get("passwordHash"));
                    library.setDbName((String) data.get("dbName"));
                    library.setInitialized((Boolean) data.getOrDefault("initialized", false));
                    
                    libraries.put(id, library);
                    logger.debug("Loaded library: {} ({})", library.getName(), id);
                }
            } else {
                logger.debug("No libraries configuration file found, will create default");
            }
        } catch (IOException e) {
            logger.error("Failed to load libraries configuration", e);
        }
    }
    
    private void createDefaultLibrary() {
        // Check if we're migrating from the old single-library system
        String existingDbName = extractDatabaseName(baseDbUrl);
        
        Library defaultLibrary = new Library(
            "main",
            "Main Library",
            "Your story collection",
            passwordEncoder.encode(appPassword),
            existingDbName
        );
        defaultLibrary.setInitialized(true);

        libraries.put("main", defaultLibrary);
        saveLibrariesToFile();

        logger.info("Created default library 'Main Library' using APP_PASSWORD");
    }
    
    private String extractDatabaseName(String jdbcUrl) {
        // Extract database name from JDBC URL like "jdbc:postgresql://db:5432/storycove"
        int lastSlash = jdbcUrl.lastIndexOf('/');
        if (lastSlash != -1 && lastSlash < jdbcUrl.length() - 1) {
            String dbPart = jdbcUrl.substring(lastSlash + 1);
            // Remove any query parameters
            int queryStart = dbPart.indexOf('?');
            return queryStart != -1 ? dbPart.substring(0, queryStart) : dbPart;
        }
        return "storycove"; // fallback
    }
    
    private void saveLibrariesToFile() {
        try {
            Map<String, Object> config = new HashMap<>();
            Map<String, Map<String, Object>> librariesData = new HashMap<>();
            
            for (Library library : libraries.values()) {
                Map<String, Object> data = new HashMap<>();
                data.put("name", library.getName());
                data.put("description", library.getDescription());
                data.put("passwordHash", library.getPasswordHash());
                data.put("dbName", library.getDbName());
                data.put("initialized", library.isInitialized());
                
                librariesData.put(library.getId(), data);
            }
            
            config.put("libraries", librariesData);
            
            // Ensure config directory exists
            new File("/app/config").mkdirs();
            
            String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(config);
            Files.writeString(Paths.get(LIBRARIES_CONFIG_PATH), json);
            
            logger.debug("Saved libraries configuration");
        } catch (IOException e) {
            logger.error("Failed to save libraries configuration", e);
        }
    }
    
    private DataSource createDataSource(String dbName) {
        String url = baseDbUrl.replaceAll("/[^/]*$", "/" + dbName);
        logger.debug("Creating DataSource for: {}", url);
        
        // First, ensure the database exists
        ensureDatabaseExists(dbName);
        
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(url);
        config.setUsername(dbUsername);
        config.setPassword(dbPassword);
        config.setDriverClassName("org.postgresql.Driver");
        config.setMaximumPoolSize(10);
        config.setConnectionTimeout(30000);
        
        return new HikariDataSource(config);
    }
    
    private void ensureDatabaseExists(String dbName) {
        // Connect to the 'postgres' database to create the new database
        String adminUrl = baseDbUrl.replaceAll("/[^/]*$", "/postgres");
        
        HikariConfig adminConfig = new HikariConfig();
        adminConfig.setJdbcUrl(adminUrl);
        adminConfig.setUsername(dbUsername);
        adminConfig.setPassword(dbPassword);
        adminConfig.setDriverClassName("org.postgresql.Driver");
        adminConfig.setMaximumPoolSize(1);
        adminConfig.setConnectionTimeout(30000);
        
        boolean databaseCreated = false;
        
        try (HikariDataSource adminDataSource = new HikariDataSource(adminConfig);
             var connection = adminDataSource.getConnection();
             var statement = connection.createStatement()) {
            
            // Check if database exists
            String checkQuery = "SELECT 1 FROM pg_database WHERE datname = ?";
            try (var preparedStatement = connection.prepareStatement(checkQuery)) {
                preparedStatement.setString(1, dbName);
                try (var resultSet = preparedStatement.executeQuery()) {
                    if (resultSet.next()) {
                        logger.debug("Database {} already exists", dbName);
                        return; // Database exists, nothing to do
                    }
                }
            }
            
            // Create database if it doesn't exist
            // Note: Database names cannot be parameterized, but we validate the name is safe
            if (!dbName.matches("^[a-zA-Z][a-zA-Z0-9_]*$")) {
                throw new IllegalArgumentException("Invalid database name: " + dbName);
            }
            
            String createQuery = "CREATE DATABASE " + dbName;
            statement.executeUpdate(createQuery);
            logger.info("Created database: {}", dbName);
            databaseCreated = true;
            
        } catch (SQLException e) {
            logger.error("Failed to ensure database {} exists: {}", dbName, e.getMessage());
            throw new RuntimeException("Database creation failed", e);
        }
        
        // If we just created the database, initialize its schema
        if (databaseCreated) {
            initializeNewDatabaseSchema(dbName);
        }
    }
    
    private void initializeNewDatabaseSchema(String dbName) {
        logger.debug("Initializing schema for new database: {}", dbName);
        
        // Create a temporary DataSource for the new database to initialize schema
        String newDbUrl = baseDbUrl.replaceAll("/[^/]*$", "/" + dbName);
        
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(newDbUrl);
        config.setUsername(dbUsername);
        config.setPassword(dbPassword);
        config.setDriverClassName("org.postgresql.Driver");
        config.setMaximumPoolSize(1);
        config.setConnectionTimeout(30000);
        
        try (HikariDataSource tempDataSource = new HikariDataSource(config)) {
            // Use Hibernate to create the schema
            // This mimics what Spring Boot does during startup
            createSchemaUsingHibernate(tempDataSource);
            logger.debug("Schema initialized for database: {}", dbName);
            
        } catch (Exception e) {
            logger.error("Failed to initialize schema for database {}: {}", dbName, e.getMessage());
            throw new RuntimeException("Schema initialization failed", e);
        }
    }
    
    public void initializeNewLibraryResources(String libraryId) {
        Library library = libraries.get(libraryId);
        if (library == null) {
            throw new IllegalArgumentException("Library not found: " + libraryId);
        }
        
        try {
            logger.debug("Initializing resources for new library: {}", library.getName());
            
            // 1. Create image directory structure
            initializeImageDirectories(library);
            
            // 2. Solr indexes are global and managed automatically
            // No per-library initialization needed for Solr
            
            logger.debug("Successfully initialized resources for library: {}", library.getName());
            
        } catch (Exception e) {
            logger.error("Failed to initialize resources for library {}: {}", libraryId, e.getMessage());
            throw new RuntimeException("Library resource initialization failed", e);
        }
    }
    
    private void initializeImageDirectories(Library library) {
        try {
            // Create the library-specific image directory
            String imagePath = "/app/images/" + library.getId();
            java.nio.file.Path libraryImagePath = java.nio.file.Paths.get(imagePath);
            
            if (!java.nio.file.Files.exists(libraryImagePath)) {
                java.nio.file.Files.createDirectories(libraryImagePath);
                logger.debug("Created image directory: {}", imagePath);
                
                // Create subdirectories for different image types
                java.nio.file.Files.createDirectories(libraryImagePath.resolve("stories"));
                java.nio.file.Files.createDirectories(libraryImagePath.resolve("authors"));  
                java.nio.file.Files.createDirectories(libraryImagePath.resolve("collections"));
                
                logger.debug("Created image subdirectories for library: {}", library.getId());
            } else {
                logger.debug("Image directory already exists: {}", imagePath);
            }
            
        } catch (Exception e) {
            logger.error("Failed to create image directories for library {}: {}", library.getId(), e.getMessage());
            throw new RuntimeException("Image directory creation failed", e);
        }
    }
    
    private void createSchemaUsingHibernate(DataSource dataSource) {
        // Create the essential tables manually using the same DDL that Hibernate would generate
        // This is simpler than setting up a full Hibernate configuration for schema creation
        
        String[] createTableStatements = {
            // Authors table
            """
            CREATE TABLE authors (
                author_rating integer,
                created_at timestamp(6) not null,
                updated_at timestamp(6) not null,
                id uuid not null,
                avatar_image_path varchar(255),
                name varchar(255) not null,
                notes TEXT,
                primary key (id)
            )
            """,
            
            // Author URLs table
            """
            CREATE TABLE author_urls (
                author_id uuid not null,
                url varchar(255)
            )
            """,
            
            // Series table
            """
            CREATE TABLE series (
                created_at timestamp(6) not null,
                id uuid not null,
                description varchar(1000),
                name varchar(255) not null,
                primary key (id)
            )
            """,
            
            // Tags table
            """
            CREATE TABLE tags (
                color varchar(7),
                created_at timestamp(6) not null,
                id uuid not null,
                description varchar(500),
                name varchar(255) not null unique,
                primary key (id)
            )
            """,
            
            // Tag aliases table
            """
            CREATE TABLE tag_aliases (
                created_from_merge boolean not null,
                created_at timestamp(6) not null,
                canonical_tag_id uuid not null,
                id uuid not null,
                alias_name varchar(255) not null unique,
                primary key (id)
            )
            """,
            
            // Collections table
            """
            CREATE TABLE collections (
                is_archived boolean not null,
                rating integer,
                created_at timestamp(6) not null,
                updated_at timestamp(6) not null,
                id uuid not null,
                cover_image_path varchar(500),
                name varchar(500) not null,
                description TEXT,
                primary key (id)
            )
            """,
            
            // Stories table
            """
            CREATE TABLE stories (
                is_read boolean,
                rating integer,
                reading_position integer,
                volume integer,
                word_count integer,
                created_at timestamp(6) not null,
                last_read_at timestamp(6),
                last_completed_at timestamp(6),
                updated_at timestamp(6) not null,
                author_id uuid,
                id uuid not null,
                series_id uuid,
                description varchar(1000),
                content_html TEXT,
                content_plain TEXT,
                cover_path varchar(255),
                source_url varchar(255),
                summary TEXT,
                title varchar(255) not null,
                primary key (id)
            )
            """,
            
            // Reading positions table
            """
            CREATE TABLE reading_positions (
                chapter_index integer,
                character_position integer,
                percentage_complete float(53),
                word_position integer,
                created_at timestamp(6) not null,
                updated_at timestamp(6) not null,
                id uuid not null,
                story_id uuid not null,
                context_after varchar(500),
                context_before varchar(500),
                chapter_title varchar(255),
                epub_cfi TEXT,
                primary key (id)
            )
            """,
            
            // Junction tables
            """
            CREATE TABLE story_tags (
                story_id uuid not null,
                tag_id uuid not null,
                primary key (story_id, tag_id)
            )
            """,
            
            """
            CREATE TABLE collection_stories (
                position integer not null,
                added_at timestamp(6) not null,
                collection_id uuid not null,
                story_id uuid not null,
                primary key (collection_id, story_id),
                unique (collection_id, position)
            )
            """,
            
            """
            CREATE TABLE collection_tags (
                collection_id uuid not null,
                tag_id uuid not null,
                primary key (collection_id, tag_id)
            )
            """
        };
        
        String[] createIndexStatements = {
            "CREATE INDEX idx_reading_position_story ON reading_positions (story_id)"
        };
        
        String[] createConstraintStatements = {
            // Foreign key constraints
            "ALTER TABLE author_urls ADD CONSTRAINT FKdqhp51m0uveybsts098gd79uo FOREIGN KEY (author_id) REFERENCES authors",
            "ALTER TABLE stories ADD CONSTRAINT FKhwecpqeaxy40ftrctef1u7gw7 FOREIGN KEY (author_id) REFERENCES authors",
            "ALTER TABLE stories ADD CONSTRAINT FK1kulyvy7wwcolp2gkndt57cp7 FOREIGN KEY (series_id) REFERENCES series",
            "ALTER TABLE reading_positions ADD CONSTRAINT FKglfhdhflan3pgyr2u0gxi21i5 FOREIGN KEY (story_id) REFERENCES stories",
            "ALTER TABLE story_tags ADD CONSTRAINT FKmans33ijt0nf65t0sng2r848j FOREIGN KEY (tag_id) REFERENCES tags",
            "ALTER TABLE story_tags ADD CONSTRAINT FKq9guid7swnjxwdpgxj3jo1rsi FOREIGN KEY (story_id) REFERENCES stories",
            "ALTER TABLE tag_aliases ADD CONSTRAINT FKqfsawmcj3ey4yycb6958y24ch FOREIGN KEY (canonical_tag_id) REFERENCES tags",
            "ALTER TABLE collection_stories ADD CONSTRAINT FKr55ho4vhj0wp03x13iskr1jds FOREIGN KEY (collection_id) REFERENCES collections",
            "ALTER TABLE collection_stories ADD CONSTRAINT FK7n41tbbrt7r2e81hpu3612r1o FOREIGN KEY (story_id) REFERENCES stories",
            "ALTER TABLE collection_tags ADD CONSTRAINT FKceq7ggev8n8ibjui1x5yo4x67 FOREIGN KEY (tag_id) REFERENCES tags",
            "ALTER TABLE collection_tags ADD CONSTRAINT FKq9sa5s8csdpbphrvb48tts8jt FOREIGN KEY (collection_id) REFERENCES collections"
        };
        
        try (var connection = dataSource.getConnection();
             var statement = connection.createStatement()) {
            
            // Create tables
            for (String sql : createTableStatements) {
                statement.executeUpdate(sql);
            }
            
            // Create indexes
            for (String sql : createIndexStatements) {
                statement.executeUpdate(sql);
            }
            
            // Create constraints
            for (String sql : createConstraintStatements) {
                statement.executeUpdate(sql);
            }
            
            logger.debug("Successfully created all database tables and constraints");
            
        } catch (SQLException e) {
            logger.error("Failed to create database schema", e);
            throw new RuntimeException("Schema creation failed", e);
        }
    }
    
    
    private void closeCurrentResources() {
        // No need to close datasource - SmartRoutingDataSource handles this
        // Solr service is managed by Spring - no explicit cleanup needed
        // Don't clear currentLibraryId here - only when explicitly switching
    }
    
    /**
     * Update library metadata (name and description)
     */
    public synchronized void updateLibraryMetadata(String libraryId, String newName, String newDescription) throws Exception {
        if (libraryId == null || libraryId.trim().isEmpty()) {
            throw new IllegalArgumentException("Library ID cannot be null or empty");
        }
        
        Library library = libraries.get(libraryId);
        if (library == null) {
            throw new IllegalArgumentException("Library not found: " + libraryId);
        }
        
        // Validate new name
        if (newName == null || newName.trim().isEmpty()) {
            throw new IllegalArgumentException("Library name cannot be null or empty");
        }
        
        String oldName = library.getName();
        String oldDescription = library.getDescription();
        
        // Update the library object
        library.setName(newName.trim());
        library.setDescription(newDescription != null ? newDescription.trim() : "");
        
        try {
            // Save to configuration file
            saveLibraryConfiguration(library);
            
            logger.info("Updated library metadata - ID: {}, Name: '{}' -> '{}', Description: '{}' -> '{}'", 
                       libraryId, oldName, newName, oldDescription, library.getDescription());
                       
        } catch (Exception e) {
            // Rollback changes on failure
            library.setName(oldName);
            library.setDescription(oldDescription);
            throw new RuntimeException("Failed to update library metadata: " + e.getMessage(), e);
        }
    }
    
    /**
     * Save library configuration to file
     */
    private void saveLibraryConfiguration(Library library) throws Exception {
        Path libraryConfigPath = libraryConfigDir.resolve(library.getId() + ".json");
        
        // Create library configuration object
        Map<String, Object> config = new HashMap<>();
        config.put("id", library.getId());
        config.put("name", library.getName());
        config.put("description", library.getDescription());
        config.put("passwordHash", library.getPasswordHash());
        config.put("dbName", library.getDbName());
        config.put("imagePath", library.getImagePath());
        config.put("initialized", library.isInitialized());
        
        // Write to file
        ObjectMapper mapper = new ObjectMapper();
        String configJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(config);
        Files.writeString(libraryConfigPath, configJson, StandardCharsets.UTF_8);
        
        logger.debug("Saved library configuration to: {}", libraryConfigPath);
    }
}