package com.adx.tracking.storage;

import com.adx.tracking.storage.mapper.*;
import com.adx.tracking.storage.model.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

import java.sql.Statement;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.List;

/**
 * 对应 Go internal/storage/tidb.go 的 TiDBColdStore。
 * 使用 MyBatis 操作 TiDB（MySQL 协议兼容）。
 * 功能：
 *   1. migrate() - 建表 DDL
 *   2. saveBid / saveTracking / saveConversion / saveMediaCallback
 *   3. successfulCallbackExists / successfulCallbackExistsForEvent
 *   4. pendingMediaCallbacks
 */
public class TiDBColdStore {

    private final SqlSessionFactory sqlSessionFactory;
    private final ObjectMapper objectMapper;

    public TiDBColdStore(SqlSessionFactory sqlSessionFactory, ObjectMapper objectMapper) {
        this.sqlSessionFactory = sqlSessionFactory;
        this.objectMapper = objectMapper;
    }

    // ─── DDL 迁移 ────────────────────────────────────────────────────────────

    public void migrate() {
        String[] ddls = {
            """
            CREATE TABLE IF NOT EXISTS ad_bid_events (
                id BIGINT AUTO_INCREMENT PRIMARY KEY,
                qk VARCHAR(128) NOT NULL,
                media VARCHAR(64),
                media_trace_id VARCHAR(255),
                req_id VARCHAR(512),
                bid_id VARCHAR(128),
                creative_id VARCHAR(128),
                imp_id VARCHAR(128),
                tag_id VARCHAR(128),
                price BIGINT UNSIGNED,
                show_urls JSON,
                click_urls JSON,
                landing_page TEXT,
                app_store_link TEXT,
                package_name VARCHAR(255),
                media_params JSON,
                created_at TIMESTAMP(3) NOT NULL,
                UNIQUE KEY uk_ad_bid_events_qk (qk),
                KEY idx_ad_bid_events_media_trace (media, media_trace_id),
                KEY idx_ad_bid_events_created_at (created_at)
            )
            """,
            """
            CREATE TABLE IF NOT EXISTS tracking_reports (
                id BIGINT AUTO_INCREMENT PRIMARY KEY,
                qk VARCHAR(128),
                kind VARCHAR(32) NOT NULL,
                tracking_url TEXT NOT NULL,
                status INT NOT NULL,
                ok TINYINT(1) NOT NULL,
                created_at TIMESTAMP(3) NOT NULL,
                KEY idx_tracking_reports_qk_kind_created_at (qk, kind, created_at)
            )
            """,
            """
            CREATE TABLE IF NOT EXISTS baidu_conversions (
                id BIGINT AUTO_INCREMENT PRIMARY KEY,
                dedupe_key VARCHAR(255),
                qk VARCHAR(128),
                media VARCHAR(64),
                date_value VARCHAR(16),
                appsid VARCHAR(128),
                customer_name VARCHAR(128),
                device_id VARCHAR(255),
                conv DOUBLE,
                payment DOUBLE,
                act INT,
                tu VARCHAR(128),
                clk_time VARCHAR(32),
                created_at TIMESTAMP(3) NOT NULL,
                UNIQUE KEY uk_baidu_conversions_dedupe (dedupe_key),
                KEY idx_baidu_conversions_qk_act_created_at (qk, act, created_at),
                KEY idx_baidu_conversions_device_id (device_id)
            )
            """,
            """
            CREATE TABLE IF NOT EXISTS media_callbacks (
                id BIGINT AUTO_INCREMENT PRIMARY KEY,
                dedupe_key VARCHAR(255),
                media VARCHAR(64) NOT NULL,
                qk VARCHAR(128),
                callback_url TEXT NOT NULL,
                event_type INT NOT NULL,
                event_time_ms BIGINT NOT NULL,
                purchase DOUBLE,
                status INT NOT NULL,
                ok TINYINT(1) NOT NULL,
                attempt INT NOT NULL DEFAULT 1,
                response_body TEXT,
                error_message TEXT,
                created_at TIMESTAMP(3) NOT NULL,
                KEY idx_media_callbacks_dedupe_ok_created_at (dedupe_key, ok, created_at),
                KEY idx_media_callbacks_qk_created_at (qk, created_at),
                KEY idx_media_callbacks_media_event_created_at (media, event_type, created_at)
            )
            """
        };
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            Statement stmt = session.getConnection().createStatement();
            for (String ddl : ddls) {
                stmt.execute(ddl);
            }
            // 字段长度补丁（已存在的表可能是旧的 128）
            try {
                stmt.execute("ALTER TABLE ad_bid_events MODIFY COLUMN req_id VARCHAR(512)");
            } catch (Exception ignored) {}
            stmt.close();
        } catch (Exception e) {
            throw new RuntimeException("migrate failed", e);
        }
    }

    // ─── SaveBid ─────────────────────────────────────────────────────────────

    public void saveBid(BidRecord record) {
        if (record.getQk() == null || record.getQk().isBlank()) {
            throw new IllegalArgumentException("qk is required");
        }
        if (record.getCreatedAt() == null) record.setCreatedAt(Instant.now());

        String showUrls   = toJson(record.getShowUrls());
        String clickUrls  = toJson(record.getClickUrls());
        String mediaParams = toJson(record.getMediaParams());

        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            BidEventMapper mapper = session.getMapper(BidEventMapper.class);
            mapper.insertOrUpdate(
                record.getQk(), record.getMedia(), record.getMediaTraceId(),
                record.getReqId(), record.getBidId(), record.getCreativeId(),
                record.getImpId(), record.getTagId(), record.getPrice(),
                showUrls, clickUrls, record.getLandingPage(),
                record.getAppStoreLink(), record.getPackageName(),
                mediaParams, Timestamp.from(record.getCreatedAt())
            );
        }
    }

    // ─── SaveTracking ─────────────────────────────────────────────────────────

    public void saveTracking(TrackingRecord record) {
        if (record.getCreatedAt() == null) record.setCreatedAt(Instant.now());
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            TrackingReportMapper mapper = session.getMapper(TrackingReportMapper.class);
            mapper.insert(
                record.getQk(), record.getKind(), record.getUrl(),
                record.getStatus(), record.isOk() ? 1 : 0,
                Timestamp.from(record.getCreatedAt())
            );
        }
    }

    // ─── SaveConversion ──────────────────────────────────────────────────────

    public void saveConversion(ConversionRecord record) {
        if (record.getCreatedAt() == null) record.setCreatedAt(Instant.now());
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            ConversionMapper mapper = session.getMapper(ConversionMapper.class);
            mapper.insertOrUpdate(
                record.getDedupeKey(), record.getQk(), record.getMedia(),
                record.getDate(), record.getAppSid(), record.getCustomerName(),
                record.getDeviceId(), record.getConv(), record.getPayment(),
                record.getAct(), record.getTu(), record.getClkTime(),
                Timestamp.from(record.getCreatedAt())
            );
        }
    }

    // ─── SaveMediaCallback ───────────────────────────────────────────────────

    public void saveMediaCallback(MediaCallbackRecord record) {
        if (record.getCreatedAt() == null) record.setCreatedAt(Instant.now());
        int attempt = record.getAttempt() <= 0 ? 1 : record.getAttempt();
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            MediaCallbackMapper mapper = session.getMapper(MediaCallbackMapper.class);
            mapper.insert(
                record.getDedupeKey(), record.getMedia(), record.getQk(),
                record.getCallbackUrl(), record.getEventType(), record.getEventTimeMs(),
                record.getPurchase(), record.getStatus(), record.isOk() ? 1 : 0,
                attempt, record.getResponseBody(), record.getErrorMessage(),
                Timestamp.from(record.getCreatedAt())
            );
        }
    }

    // ─── 查询：成功回调是否存在 ────────────────────────────────────────────────

    public boolean successfulCallbackExists(String dedupeKey) {
        if (dedupeKey == null || dedupeKey.isBlank()) return false;
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            MediaCallbackMapper mapper = session.getMapper(MediaCallbackMapper.class);
            int count = mapper.countSuccessfulByDedupeKey(dedupeKey);
            return count > 0;
        }
    }

    public boolean successfulCallbackExistsForEvent(String dedupeKey, String legacyDedupeKey, int eventType) {
        if (dedupeKey == null || dedupeKey.isBlank()) return false;
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            MediaCallbackMapper mapper = session.getMapper(MediaCallbackMapper.class);
            int count = mapper.countSuccessfulForEvent(dedupeKey, legacyDedupeKey, eventType);
            return count > 0;
        }
    }

    // ─── 查询：待重试的回调 ────────────────────────────────────────────────────

    public List<MediaCallbackRecord> pendingMediaCallbacks(String media, int limit) {
        if (limit <= 0) limit = 100;
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            MediaCallbackMapper mapper = session.getMapper(MediaCallbackMapper.class);
            return mapper.selectPendingCallbacks(media, limit);
        }
    }

    // ─── 工具方法 ─────────────────────────────────────────────────────────────

    private String toJson(Object value) {
        if (value == null) return "null";
        try {
            return objectMapper.writeValueAsString(value);
        } catch (Exception e) {
            throw new RuntimeException("JSON serialize failed", e);
        }
    }
}
