"""SQLite 캔들 저장소."""

from __future__ import annotations

import logging
import sqlite3
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Any

from deepcoin.api.bithumb import parse_kst_datetime

logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class CandleRow:
    """DB에 저장할 단일 캔들 행."""

    code: str
    name: str
    ymdhms: str
    ymd: str
    hms: str
    close: float
    open: float
    high: float
    low: float
    volume: float


def table_name(symbol: str, interval_min: int) -> str:
    """심볼·인터벌에 대응하는 테이블명을 반환한다.

    Args:
        symbol: 코인 심볼 (예: WLD).
        interval_min: 분 단위 (1440=일봉).

    Returns:
        테이블명 (예: WLD_60).
    """
    return f"{symbol.upper()}_{interval_min}"


def candle_to_row(
    candle: dict[str, Any],
    symbol: str,
    coin_name: str,
) -> CandleRow:
    """빗썸 API 캔들 dict를 CandleRow로 변환한다.

    Args:
        candle: API 응답 캔들 객체.
        symbol: 코인 심볼.
        coin_name: 코인 한글명.

    Returns:
        CandleRow.
    """
    kst_raw = candle["candle_date_time_kst"]
    dt = parse_kst_datetime(kst_raw)
    ymdhms = dt.strftime("%Y-%m-%d %H:%M:%S")
    return CandleRow(
        code=symbol.upper(),
        name=coin_name,
        ymdhms=ymdhms,
        ymd=dt.strftime("%Y-%m-%d"),
        hms=dt.strftime("%H:%M:%S"),
        close=float(candle["trade_price"]),
        open=float(candle["opening_price"]),
        high=float(candle["high_price"]),
        low=float(candle["low_price"]),
        volume=float(candle["candle_acc_trade_volume"]),
    )


class CandleStore:
    """SQLite 기반 캔들 저장소."""

    def __init__(self, db_path: Path) -> None:
        """저장소를 연다.

        Args:
            db_path: SQLite DB 파일 경로.
        """
        self.db_path = db_path
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._conn = sqlite3.connect(self.db_path)
        self._conn.execute("PRAGMA journal_mode=WAL")

    def close(self) -> None:
        """DB 연결을 닫는다."""
        self._conn.close()

    def ensure_table(self, symbol: str, interval_min: int) -> str:
        """테이블과 인덱스를 보장한다.

        Args:
            symbol: 코인 심볼.
            interval_min: 분 단위.

        Returns:
            테이블명.
        """
        name = table_name(symbol, interval_min)
        self._conn.execute(
            f"""
            CREATE TABLE IF NOT EXISTS {name} (
                CODE text,
                NAME text,
                ymdhms datetime,
                ymd text,
                hms text,
                Close REAL,
                Open REAL,
                High REAL,
                Low REAL,
                Volume REAL
            )
            """
        )
        self._conn.execute(
            f"CREATE INDEX IF NOT EXISTS {name}_idx ON {name}(CODE, ymdhms)"
        )
        self._ensure_unique_index(name)
        self._conn.commit()
        return name

    def _ensure_unique_index(self, table: str) -> None:
        """(CODE, ymdhms) 유니크 인덱스를 보장한다. 중복이 있으면 제거 후 생성.

        Args:
            table: 테이블명.
        """
        index_name = f"{table}_uk"
        existing = self._conn.execute(
            "SELECT name FROM sqlite_master WHERE type='index' AND name=?",
            (index_name,),
        ).fetchone()
        if existing:
            return

        self._conn.execute(
            f"""
            DELETE FROM {table}
            WHERE rowid NOT IN (
                SELECT MAX(rowid) FROM {table} GROUP BY CODE, ymdhms
            )
            """
        )
        self._conn.execute(
            f"CREATE UNIQUE INDEX IF NOT EXISTS {index_name} ON {table}(CODE, ymdhms)"
        )

    def upsert_rows(self, table: str, rows: list[CandleRow]) -> int:
        """캔들 행을 upsert한다.

        Args:
            table: 대상 테이블명.
            rows: 저장할 행 목록.

        Returns:
            저장(갱신)된 행 수.
        """
        if not rows:
            return 0
        sql = f"""
            INSERT OR REPLACE INTO {table}
            (CODE, NAME, ymdhms, ymd, hms, Close, Open, High, Low, Volume)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """
        payload = [
            (
                row.code,
                row.name,
                row.ymdhms,
                row.ymd,
                row.hms,
                row.close,
                row.open,
                row.high,
                row.low,
                row.volume,
            )
            for row in rows
        ]
        with self._conn:
            self._conn.executemany(sql, payload)
        return len(rows)

    def get_range(self, symbol: str, interval_min: int) -> tuple[int, datetime | None, datetime | None]:
        """저장된 캔들 범위를 조회한다.

        Args:
            symbol: 코인 심볼.
            interval_min: 분 단위.

        Returns:
            (행 수, 최소 시각, 최대 시각). 데이터 없으면 (0, None, None).
        """
        table = table_name(symbol, interval_min)
        try:
            cursor = self._conn.execute(
                f"SELECT COUNT(*), MIN(ymdhms), MAX(ymdhms) FROM {table}"
            )
            count, min_raw, max_raw = cursor.fetchone()
        except sqlite3.OperationalError:
            return 0, None, None

        if not count:
            return 0, None, None

        min_dt = datetime.strptime(min_raw, "%Y-%m-%d %H:%M:%S") if min_raw else None
        max_dt = datetime.strptime(max_raw, "%Y-%m-%d %H:%M:%S") if max_raw else None
        return int(count), min_dt, max_dt
