package com.magistr.app.config.tenant;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.magistr.app.config.tenant.health.TenantReadinessRegistry;
import com.magistr.app.service.TenantDatabaseMigrationService;
import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

import jakarta.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.io.File;
import java.sql.Connection;
import java.util.*;

/**
 * Конфигурация мультитенантного DataSource.
 * Загружает тенанты из JSON-файла (смонтированный Kubernetes Secret).
 *
 * Если нет ни одного настроенного тенанта — создаёт H2 in-memory БД
 * как заглушку, чтобы Spring JPA мог инициализироваться.
 */
@Configuration
public class TenantDataSourceConfig {

    private static final Logger log = LoggerFactory.getLogger(TenantDataSourceConfig.class);

    @Value("${app.tenants.config-path:tenants.json}")
    private String tenantsConfigPath;

    @Value("${app.tenants.config-required:false}")
    private boolean tenantsConfigRequired;

    @Value("${spring.datasource.url:}")
    private String defaultDbUrl;

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

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

    @Bean
    @Primary
    public DataSource dataSource(TenantDatabaseMigrationService migrationService,
                                 TenantReadinessRegistry readinessRegistry) {
        TenantRoutingDataSource routingDataSource = new TenantRoutingDataSource();

        // Загружаем тенантов из JSON (read-only Secret mount)
        TenantConfigLoadResult loadResult = loadTenantsFromFile();
        List<TenantConfig> tenants = new ArrayList<>(loadResult.tenants());

        // Если нет тенантов и есть дефолтный datasource — создаём "default" тенант
        if (tenants.isEmpty() && defaultDbUrl != null && !defaultDbUrl.isBlank()) {
            TenantConfig defaultTenant = new TenantConfig(
                "Default", "default", defaultDbUrl, defaultDbUsername, defaultDbPassword
            );
            tenants.add(defaultTenant);
            log.info("Конфигурация тенантов отсутствует, используется источник данных по умолчанию");
        }

        readinessRegistry.clearConfigurationFailure();
        readinessRegistry.replaceDesired(tenants);
        if (loadResult.failed() || (tenantsConfigRequired && loadResult.tenants().isEmpty())) {
            readinessRegistry.markConfigurationFailure();
        }

        // Регистрируем тенантов
        for (TenantConfig tenant : tenants) {
            registerPreparedTenant(routingDataSource, migrationService, readinessRegistry, tenant, true);
        }

        // Если всё ещё нет ни одного тенанта — H2-заглушка в памяти
        if (routingDataSource.getTenantConfigs().isEmpty()) {
            log.warn("=== НЕТ НАСТРОЕННЫХ ТЕНАНТОВ ===");
            log.warn("Создаём H2-заглушку в памяти для запуска приложения");
            log.warn("Добавьте тенант через POST /api/database/tenants");

            TenantConfig h2Fallback = new TenantConfig(
                "H2 Placeholder", "default",
                "jdbc:h2:mem:placeholder;DB_CLOSE_DELAY=-1",
                "sa", ""
            );
            if (!registerPreparedTenant(
                    routingDataSource,
                    migrationService,
                    readinessRegistry,
                    h2Fallback,
                    false
            )) {
                throw new IllegalStateException("Не удалось создать резервный H2 DataSource");
            }
        }

        return routingDataSource;
    }

    @Bean
    public TenantRoutingDataSource tenantRoutingDataSource(DataSource dataSource) {
        return (TenantRoutingDataSource) dataSource;
    }

    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("com.magistr.app.model");

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(false);
        vendorAdapter.setDatabasePlatform("org.hibernate.dialect.PostgreSQLDialect");
        em.setJpaVendorAdapter(vendorAdapter);

        Map<String, Object> props = new HashMap<>();
        props.put("hibernate.hbm2ddl.auto", "none");
        props.put("hibernate.show_sql", "false");
        em.setJpaPropertyMap(props);

        return em;
    }

    @Bean
    @Primary
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }

    private TenantConfigLoadResult loadTenantsFromFile() {
        File file = new File(tenantsConfigPath);
        if (!file.exists()) {
            if (tenantsConfigRequired) {
                log.error("Обязательный файл конфигурации тенантов не найден");
            } else {
                log.info("Файл конфигурации тенантов не найден");
            }
            return new TenantConfigLoadResult(List.of(), tenantsConfigRequired);
        }

        try {
            ObjectMapper mapper = new ObjectMapper();
            List<TenantConfig> list = mapper.readValue(file, new TypeReference<>() {});
            if (list == null) {
                throw new IllegalArgumentException("Корневое значение конфигурации тенантов должно быть массивом");
            }
            log.info("Загружено конфигураций тенантов: {}", list.size());
            return new TenantConfigLoadResult(List.copyOf(list), false);
        } catch (Exception e) {
            log.error("Не удалось прочитать конфигурацию тенантов: типОшибки={}",
                    e.getClass().getSimpleName());
            log.debug("Технические детали чтения конфигурации тенантов", e);
            return new TenantConfigLoadResult(List.of(), true);
        }
    }

    private boolean registerPreparedTenant(TenantRoutingDataSource routingDataSource,
                                           TenantDatabaseMigrationService migrationService,
                                           TenantReadinessRegistry readinessRegistry,
                                           TenantConfig tenant,
                                           boolean requiredForReadiness) {
        HikariDataSource candidate = null;
        boolean activated = false;
        if (requiredForReadiness) {
            readinessRegistry.markMigrationStarted(tenant);
        }
        try {
            candidate = routingDataSource.prepareTenantDataSource(tenant);
            try (Connection connection = candidate.getConnection()) {
                if (!connection.isValid(5)) {
                    throw new IllegalStateException("База данных не подтвердила готовность подключения");
                }
            }
            if (!tenant.getUrl().trim().startsWith("jdbc:h2:")) {
                migrationService.migrate(candidate);
            }
            TenantRoutingDataSource.TenantState previous = routingDataSource.swapTenant(tenant, candidate);
            activated = true;
            if (requiredForReadiness) {
                readinessRegistry.markMigrationSucceeded(tenant);
                readinessRegistry.markConnectivity(tenant.getDomain(), true);
            }
            closeDataSource(previous == null ? null : previous.dataSource());
            log.info("БД тенанта '{}' проверена и активирована", tenant.getDomain());
            return true;
        } catch (Exception startupFailure) {
            if (requiredForReadiness) {
                readinessRegistry.markMigrationFailed(tenant);
            }
            log.error("Не удалось безопасно активировать БД тенанта '{}': типОшибки={}",
                    tenant.getDomain(), startupFailure.getClass().getSimpleName());
            log.debug("Технические детали жизненного цикла запуска БД тенанта", startupFailure);
            return false;
        } finally {
            if (!activated) {
                closeDataSource(candidate);
            }
        }
    }

    private void closeDataSource(DataSource dataSource) {
        if (dataSource instanceof HikariDataSource hikariDataSource) {
            try {
                hikariDataSource.close();
            } catch (RuntimeException closeFailure) {
                log.warn("Не удалось штатно закрыть стартовый пул Hikari: типОшибки={}",
                        closeFailure.getClass().getSimpleName());
                log.debug("Технические детали закрытия стартового пула Hikari", closeFailure);
            }
        }
    }

    private record TenantConfigLoadResult(List<TenantConfig> tenants, boolean failed) {
    }
}
