package com.erp.crosstenant;

import com.erp.tenant.DbContextHolder;
import com.erp.tenant.TenantDataSourceFactory;
import com.erp.tenant.TenantDbSettings;
import com.erp.tenant.TenantRoutingDataSource;
import com.zaxxer.hikari.HikariDataSource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

/**
 * SUPER_ADMIN 의 cross-tenant WRITE 트랙 — 회사 컨텍스트 임시 전환 + 작업 실행 + 복원.
 *
 * READ 트랙({@link CrossTenantAggregator}) 와 달리 fan-out 안 함. 호출자가 명시한
 * 단일 회사(company_code) DB 컨텍스트로 잠깐 전환해서 INSERT/UPDATE/DELETE 실행.
 *
 * 사용 패턴 (컨트롤러):
 * <pre>
 *   if (!CrossTenantContext.isSuperAdmin(request)) return forbidden();
 *   if (!CrossTenantContext.isMetaContext()) return badRequest();
 *
 *   String targetCompany = (String) body.get("company_code");
 *   Map&lt;String,Object&gt; result = executor.runInCompany(targetCompany, () -&gt;
 *       adminService.saveUser(body)
 *   );
 * </pre>
 *
 * 핵심 보장:
 *   - 회사 풀 lazy 생성 (없으면 만들고, 이미 있으면 no-op). minIdle=0 정책 그대로.
 *   - finally 에서 prev 컨텍스트 복원 — 누수되면 후속 요청이 엉뚱한 회사 DB 로 라우팅.
 *   - 알 수 없는 company_code 면 IllegalArgumentException — 컨트롤러가 400 으로 변환.
 *
 * @see CrossTenantAggregator  // READ 트랙 (fan-out)
 * @see CrossTenantContext     // 가드
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class CrossTenantExecutor {

    private final SqlSession sqlSession;
    private final TenantRoutingDataSource routingDataSource;
    private final TenantDbSettings tenantDbSettings;

    /**
     * 지정 회사 DB 컨텍스트로 작업 실행. 결과 반환.
     *
     * @throws IllegalArgumentException company_code 가 비어있거나 active 회사가 아닐 때
     */
    public <T> T runInCompany(String companyCode, Supplier<T> work) {
        String dbName = resolveDbName(companyCode);

        ensureTenantPool(dbName);

        String prev = DbContextHolder.get();
        try {
            DbContextHolder.set(dbName);
            log.info("[CrossTenant/Write] enter company={} db={}", companyCode, dbName);
            return work.get();
        } finally {
            if (prev == null) {
                DbContextHolder.clear();
            } else {
                DbContextHolder.set(prev);
            }
        }
    }

    /** 결과 없는 작업용 (Runnable 형태). */
    public void runInCompany(String companyCode, Runnable work) {
        runInCompany(companyCode, () -> {
            work.run();
            return null;
        });
    }

    /**
     * company_code → db_name 매핑. META DB 의 COMPANY_MNG 에서 active 행만 인정.
     * 컨텍스트 전환 전에 호출돼야 하므로 META 컨텍스트(현재 컨텍스트)에서 실행.
     */
    private String resolveDbName(String companyCode) {
        if (companyCode == null || companyCode.isBlank()) {
            throw new IllegalArgumentException("company_code 가 비어있음");
        }
        if ("*".equals(companyCode)) {
            throw new IllegalArgumentException("'*' 는 cross-tenant write 대상이 아님 (메타 = SUPER_ADMIN 자신)");
        }
        Map<String, Object> params = new HashMap<>();
        params.put("company_code", companyCode);
        String dbName = sqlSession.selectOne("provisioning.resolveDbNameByCompanyCode", params);
        if (dbName == null) {
            throw new IllegalArgumentException("등록되지 않았거나 비활성 회사: company_code=" + companyCode);
        }
        return dbName;
    }

    /** 회사 풀이 없으면 최초 1회 생성. SubdomainResolverFilter / Aggregator 와 동일 패턴. */
    private void ensureTenantPool(String dbName) {
        if (routingDataSource.hasTenant(dbName)) return;
        synchronized (routingDataSource) {
            if (routingDataSource.hasTenant(dbName)) return;
            HikariDataSource ds = TenantDataSourceFactory.createTenant(
                    tenantDbSettings.buildJdbcUrl(dbName),
                    tenantDbSettings.username(),
                    tenantDbSettings.password(),
                    dbName);
            routingDataSource.addTenant(dbName, ds);
        }
    }
}
