package com.adx.tracking;

import com.adx.tracking.baidu.AdxClient;
import com.adx.tracking.baidu.AuctionPriceEncoder;
import com.adx.tracking.baidu.ConversionClient;
import com.adx.tracking.config.AppProperties;
import com.adx.tracking.conversionsync.ConversionSyncRunner;
import com.adx.tracking.conversionsync.ConversionSyncService;
import com.adx.tracking.conversionsync.RetryService;
import com.adx.tracking.httpapi.MediaPlacement;
import com.adx.tracking.leader.LeaderElection;
import com.adx.tracking.media.kuaishou.KuaishouClient;
import com.adx.tracking.storage.RedisHotStore;
import com.adx.tracking.storage.RedisLock;
import com.adx.tracking.storage.TiDBColdStore;
import com.adx.tracking.worker.ColdWorker;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import org.springframework.lang.Nullable;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

/**
 * 对应 Go cmd/server/main.go 的 main()。
 * 负责所有 Bean 的创建和后台任务（Worker、Leader 选举、定时同步等）。
 */
@Configuration
public class AppConfiguration {

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

    @Autowired private AppProperties props;

    // ─── Redis ───────────────────────────────────────────────────────────────

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        String addr = props.getRedisAddr();
        String[] parts = addr.split(":", 2);
        String host = parts[0];
        int port = parts.length > 1 ? Integer.parseInt(parts[1]) : 6379;

        RedisStandaloneConfiguration cfg = new RedisStandaloneConfiguration(host, port);
        cfg.setDatabase(props.getRedisDb());
        if (props.getRedisUsername() != null && !props.getRedisUsername().isBlank()) {
            cfg.setUsername(props.getRedisUsername());
        }
        if (props.getRedisPassword() != null && !props.getRedisPassword().isBlank()) {
            cfg.setPassword(props.getRedisPassword());
        }
        return new LettuceConnectionFactory(cfg);
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
        return new StringRedisTemplate(factory);
    }

    @Bean
    public RedisHotStore redisHotStore(StringRedisTemplate redis, ObjectMapper objectMapper) {
        return new RedisHotStore(redis, objectMapper,
                "adx:",
                props.getRedisStream(),
                props.getBidTtl());
    }

    @Bean
    public RedisLock redisLock(StringRedisTemplate redis) {
        return new RedisLock(redis);
    }

    @Bean
    public LeaderElection leaderElection(RedisLock redisLock) {
        return new LeaderElection(redisLock);
    }

    // ─── TiDB（可选）────────────────────────────────────────────────────────

    @Bean
    public TiDBColdStore tiDBColdStore(ObjectMapper objectMapper) {
        String url = props.getTidbUrl();
        if (url == null || url.isBlank()) {
            log.info("TiDB URL is empty; cold storage disabled");
            return null;
        }
        try {
            DataSource ds = createDataSource(url, props.getTidbUsername(), props.getTidbPassword());
            SqlSessionFactory sqlSessionFactory = createSqlSessionFactory(ds);
            TiDBColdStore store = new TiDBColdStore(sqlSessionFactory, objectMapper);
            store.migrate();
            log.info("TiDB cold store initialized and migrated");
            return store;
        } catch (Exception e) {
            throw new RuntimeException("Failed to open TiDB: " + e.getMessage(), e);
        }
    }

    private SqlSessionFactory createSqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return factoryBean.getObject();
    }

    private DataSource createDataSource(String jdbcUrl, String username, String password) {
        org.springframework.jdbc.datasource.DriverManagerDataSource ds =
                new org.springframework.jdbc.datasource.DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl(jdbcUrl);
        if (username != null && !username.isBlank()) ds.setUsername(username);
        if (password != null && !password.isBlank()) ds.setPassword(password);
        return ds;
    }

    // ─── Baidu 客户端 ────────────────────────────────────────────────────────

    @Bean
    public AuctionPriceEncoder auctionPriceEncoder() {
        return AuctionPriceEncoder.create(
                props.getBaiduAuctionPriceEncryption(),
                props.getBaiduAuctionPriceEKey(),
                props.getBaiduAuctionPriceIKey());
    }

    @Bean
    public AdxClient adxClient(AuctionPriceEncoder encoder, ObjectMapper objectMapper) {
        return new AdxClient(props.getBaiduAdxEndpoint(), encoder, objectMapper);
    }

    @Bean
    public ConversionClient conversionClient(ObjectMapper objectMapper) {
        return new ConversionClient(
                props.getBaiduConversionBaseUrl(),
                props.getBaiduCustomerName(),
                props.getBaiduSecret(),
                objectMapper);
    }

    // ─── Kuaishou 客户端 + Sync 服务 ────────────────────────────────────────

    @Bean
    public KuaishouClient kuaishouClient() {
        return new KuaishouClient(props.getKuaishouActMap(), props.getKuaishouExtraActMap());
    }

    @Bean
    public ConversionSyncService conversionSyncService(ConversionClient baiduClient,
                                                         RedisHotStore hotStore,
                                                         KuaishouClient kuaishouClient,
                                                         @Nullable TiDBColdStore coldStore) {
        if (coldStore == null) return null;
        return new ConversionSyncService(baiduClient, hotStore, kuaishouClient, coldStore);
    }

    @Bean
    public ConversionSyncRunner conversionSyncRunner(@Nullable ConversionSyncService syncer) {
        if (syncer == null) return null;
        return new ConversionSyncRunner(syncer,
                props.getConversionSyncDateOffsetDays(),
                props.getConversionSyncPageSize(),
                props.getConversionSyncActs());
    }

    @Bean
    public RetryService retryService(@Nullable TiDBColdStore coldStore, KuaishouClient kuaishouClient) {
        if (coldStore == null) return null;
        return new RetryService(coldStore, kuaishouClient, props.getCallbackRetryLimit());
    }

    // ─── ColdWorker ─────────────────────────────────────────────────────────

    @Bean
    public ColdWorker coldWorker(RedisHotStore hotStore, @Nullable TiDBColdStore coldStore,
                                  ObjectMapper objectMapper) {
        if (coldStore == null) return null;
        return new ColdWorker(hotStore, coldStore,
                props.getWorkerGroup(), props.getWorkerConsumer(),
                props.getWorkerBatch(), null,
                props.getRedisStreamMaxLen(), objectMapper);
    }

    // ─── MediaPlacement（快手）───────────────────────────────────────────────

    @Bean
    public MediaPlacement kuaishouPlacement() {
        MediaPlacement p = new MediaPlacement();
        p.setBaiduMediaId(props.getBaiduMediaId());
        p.setBaiduAppId(props.getBaiduAppId());
        p.setBaiduTagId(props.getBaiduTagId());
        p.setBidFloor(props.getBaiduBidFloor());
        p.setActionTypes(List.of(props.getBaiduActionType()));

        Map<String, MediaPlacement.BaiduAppPlacement> platforms = new HashMap<>();
        if (props.getBaiduAndroidAppId() != null && !props.getBaiduAndroidAppId().isBlank()) {
            MediaPlacement.BaiduAppPlacement android = new MediaPlacement.BaiduAppPlacement();
            android.setAppId(props.getBaiduAndroidAppId());
            android.setTagIds(props.getBaiduAndroidTagIds());
            platforms.put("android", android);
        }
        if (props.getBaiduIosAppId() != null && !props.getBaiduIosAppId().isBlank()) {
            MediaPlacement.BaiduAppPlacement ios = new MediaPlacement.BaiduAppPlacement();
            ios.setAppId(props.getBaiduIosAppId());
            ios.setTagIds(props.getBaiduIosTagIds());
            platforms.put("ios", ios);
        }
        if (!platforms.isEmpty()) p.setPlatforms(platforms);
        return p;
    }

    // ─── 后台任务调度 ────────────────────────────────────────────────────────

    /**
     * 对应 Go runColdWorker()：持续轮询 Redis Stream → 写 TiDB。
     * 在应用启动后在独立线程中运行。
     */
    @Component
    static class BackgroundTasks implements ApplicationRunner {

        private static final Logger taskLog = LoggerFactory.getLogger(BackgroundTasks.class);

        @Autowired private AppProperties props;
        @Autowired(required = false) private ColdWorker coldWorker;
        @Autowired(required = false) private ConversionSyncRunner conversionSyncRunner;
        @Autowired(required = false) private RetryService retryService;
        @Autowired(required = false) private LeaderElection leaderElection;

        private final AtomicBoolean stopped = new AtomicBoolean(false);
        private final ExecutorService executor = Executors.newCachedThreadPool(r -> {
            Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
        });

        @Override
        public void run(ApplicationArguments args) {
            // 冷路径 Worker
            if (coldWorker != null) {
                executor.submit(() -> {
                    while (!stopped.get()) {
                        try {
                            coldWorker.runOnce();
                        } catch (Exception e) {
                            taskLog.error("cold worker: {}", e.getMessage(), e);
                            sleep(1000);
                        }
                    }
                });
                taskLog.info("Cold worker started");
            }

            // 转化同步
            if (conversionSyncRunner != null && props.isConversionSyncEnabled()) {
                if (props.isSkipLeaderElection()) {
                    executor.submit(() -> runConversionSync(() -> stopped.get()));
                    taskLog.info("Conversion sync started (skip leader election)");
                } else if (leaderElection != null) {
                    executor.submit(() ->
                        leaderElection.run(
                            "adx:lock:kuaishou:conversion-sync",
                            props.getTaskLockTtl(), props.getTaskLockRenewInterval(), props.getTaskLockRetryInterval(),
                            stopped::get,
                            (jobStop) -> runConversionSync(jobStop),
                            e -> taskLog.error("kuaishou conversion sync leader: {}", e.getMessage(), e)
                        )
                    );
                    taskLog.info("Conversion sync leader election started");
                }
            } else {
                taskLog.warn("Conversion sync NOT started: runner={}, enabled={}",
                        conversionSyncRunner != null, props.isConversionSyncEnabled());
            }

            // 回调重试
            if (retryService != null && props.isCallbackRetryEnabled()) {
                if (props.isSkipLeaderElection()) {
                    executor.submit(() -> runCallbackRetry(() -> stopped.get()));
                    taskLog.info("Callback retry started (skip leader election)");
                } else if (leaderElection != null) {
                    executor.submit(() ->
                        leaderElection.run(
                            "adx:lock:kuaishou:callback-retry",
                            props.getTaskLockTtl(), props.getTaskLockRenewInterval(), props.getTaskLockRetryInterval(),
                            stopped::get,
                            (jobStop) -> runCallbackRetry(jobStop),
                            e -> taskLog.error("kuaishou callback retry leader: {}", e.getMessage(), e)
                        )
                    );
                    taskLog.info("Callback retry leader election started");
                }
            } else {
                taskLog.warn("Callback retry NOT started: retryService={}, enabled={}",
                        retryService != null, props.isCallbackRetryEnabled());
            }
        }

        private void runConversionSync(LeaderElection.StopSignal jobStop) {
            long intervalMs = props.getConversionSyncInterval().toMillis();
            taskLog.info("[ConversionSync] task started, interval={}ms", intervalMs);
            while (!jobStop.isStopped()) {
                try {
                    taskLog.info("[ConversionSync] executing...");
                    ConversionSyncService.SyncResult r = conversionSyncRunner.runOnce();
                    taskLog.info("[ConversionSync] done: fetched={} sent={} failed={}", r.fetched, r.sent, r.failed);
                } catch (Exception e) {
                    taskLog.error("[ConversionSync] error: {}", e.getMessage(), e);
                }
                taskLog.info("[ConversionSync] sleeping {}ms until next run", intervalMs);
                sleepResponsive(intervalMs, jobStop);
            }
            taskLog.info("[ConversionSync] task stopped");
        }

        private void runCallbackRetry(LeaderElection.StopSignal jobStop) {
            long intervalMs = props.getCallbackRetryInterval().toMillis();
            taskLog.info("[CallbackRetry] task started, interval={}ms, limit={}", intervalMs, props.getCallbackRetryLimit());
            while (!jobStop.isStopped()) {
                try {
                    taskLog.info("[CallbackRetry] executing...");
                    RetryService.RetryResult r = retryService.retryKuaishouCallbacks(props.getCallbackRetryLimit());
                    taskLog.info("[CallbackRetry] done: fetched={} sent={} failed={}", r.fetched, r.sent, r.failed);
                } catch (Exception e) {
                    taskLog.error("[CallbackRetry] error: {}", e.getMessage(), e);
                }
                taskLog.info("[CallbackRetry] sleeping {}ms until next run", intervalMs);
                sleepResponsive(intervalMs, jobStop);
            }
            taskLog.info("[CallbackRetry] task stopped");
        }

        /**
         * 响应式 sleep：每 200ms 检查一次 jobStop，锁丢失时立即退出。
         * 对应 Go 的 select { case <-ctx.Done(): return; case <-ticker.C: } 语义。
         */
        private static void sleepResponsive(long ms, LeaderElection.StopSignal jobStop) {
            long deadline = System.currentTimeMillis() + ms;
            while (!jobStop.isStopped()) {
                long remaining = deadline - System.currentTimeMillis();
                if (remaining <= 0) break;
                try {
                    Thread.sleep(Math.min(remaining, 200));
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }

        private static void sleep(long ms) {
            try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        }
    }
}
