package com.adx.tracking.storage;

import com.adx.tracking.storage.model.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

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

    private final JdbcTemplate jdbc;
    private final ObjectMapper objectMapper;

    public TiDBColdStore(DataSource dataSource, ObjectMapper objectMapper) {
        this.jdbc = new JdbcTemplate(dataSource);
        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)
            )
            """
        };
        for (String ddl : ddls) {
            jdbc.execute(ddl);
        }
        // 字段长度补丁（已存在的表可能是旧的 128）
        try {
            jdbc.execute("ALTER TABLE ad_bid_events MODIFY COLUMN req_id VARCHAR(512)");
        } catch (Exception ignored) {}
    }

    // ─── 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());

        jdbc.update("""
            INSERT INTO ad_bid_events (
                qk, media, media_trace_id, req_id, bid_id, creative_id, imp_id, tag_id, price,
                show_urls, click_urls, landing_page, app_store_link, package_name,
                media_params, created_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON DUPLICATE KEY UPDATE
                media = VALUES(media),
                media_trace_id = VALUES(media_trace_id),
                req_id = VALUES(req_id),
                bid_id = VALUES(bid_id),
                creative_id = VALUES(creative_id),
                imp_id = VALUES(imp_id),
                tag_id = VALUES(tag_id),
                price = VALUES(price),
                show_urls = VALUES(show_urls),
                click_urls = VALUES(click_urls),
                landing_page = VALUES(landing_page),
                app_store_link = VALUES(app_store_link),
                package_name = VALUES(package_name),
                media_params = VALUES(media_params)
            """,
            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());
        jdbc.update("""
            INSERT INTO tracking_reports (qk, kind, tracking_url, status, ok, created_at)
            VALUES (?, ?, ?, ?, ?, ?)
            """,
            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());
        jdbc.update("""
            INSERT INTO baidu_conversions (
                dedupe_key, qk, media, date_value, appsid, customer_name, device_id, conv,
                payment, act, tu, clk_time, created_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON DUPLICATE KEY UPDATE
                media = VALUES(media),
                conv = VALUES(conv),
                payment = VALUES(payment),
                clk_time = VALUES(clk_time)
            """,
            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();
        jdbc.update("""
            INSERT INTO media_callbacks (
                dedupe_key, media, qk, callback_url, event_type, event_time_ms, purchase,
                status, ok, attempt, response_body, error_message, created_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            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;
        Integer count = jdbc.queryForObject(
                "SELECT COUNT(1) FROM media_callbacks WHERE dedupe_key = ? AND ok = 1 LIMIT 1",
                Integer.class, dedupeKey);
        return count != null && count > 0;
    }

    public boolean successfulCallbackExistsForEvent(String dedupeKey, String legacyDedupeKey, int eventType) {
        if (dedupeKey == null || dedupeKey.isBlank()) return false;
        Integer count = jdbc.queryForObject("""
            SELECT COUNT(1)
            FROM media_callbacks
            WHERE ok = 1
              AND (
                dedupe_key = ?
                OR (dedupe_key = ? AND event_type = ?)
              )
            LIMIT 1
            """, Integer.class, dedupeKey, legacyDedupeKey, eventType);
        return count != null && count > 0;
    }

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

    public List<MediaCallbackRecord> pendingMediaCallbacks(String media, int limit) {
        if (limit <= 0) limit = 100;
        return jdbc.query("""
            SELECT m.dedupe_key, m.media, m.qk, m.callback_url, m.event_type, m.event_time_ms,
                   m.purchase, m.status, m.ok, m.attempt, m.response_body, m.error_message, m.created_at
            FROM media_callbacks m
            JOIN (
                SELECT dedupe_key, MAX(id) AS id
                FROM media_callbacks
                WHERE media = ? AND dedupe_key IS NOT NULL AND dedupe_key != ''
                GROUP BY dedupe_key
            ) latest ON latest.id = m.id
            WHERE m.media = ? AND m.ok = 0
              AND NOT EXISTS (
                SELECT 1 FROM media_callbacks ok_cb
                WHERE ok_cb.dedupe_key = m.dedupe_key AND ok_cb.ok = 1
              )
            ORDER BY m.created_at ASC
            LIMIT ?
            """,
            (rs, rowNum) -> {
                MediaCallbackRecord r = new MediaCallbackRecord();
                r.setDedupeKey(rs.getString("dedupe_key"));
                r.setMedia(rs.getString("media"));
                r.setQk(rs.getString("qk"));
                r.setCallbackUrl(rs.getString("callback_url"));
                r.setEventType(rs.getInt("event_type"));
                r.setEventTimeMs(rs.getLong("event_time_ms"));
                r.setPurchase(rs.getDouble("purchase"));
                r.setStatus(rs.getInt("status"));
                r.setOk(rs.getInt("ok") == 1);
                r.setAttempt(rs.getInt("attempt"));
                r.setResponseBody(rs.getString("response_body"));
                r.setErrorMessage(rs.getString("error_message"));
                Timestamp ts = rs.getTimestamp("created_at");
                if (ts != null) r.setCreatedAt(ts.toInstant());
                return r;
            },
            media, 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);
        }
    }
}
