from __future__ import annotations

import json
import re
import sqlite3
from datetime import datetime
from typing import Any

from backend.data.contracts import MarketEntity


class MarketRepository:
    def replace_calendar(
        self,
        connection: sqlite3.Connection,
        rows: tuple[dict[str, Any], ...],
        source: str,
        observed_at: str,
    ) -> None:
        connection.executemany(
            """
            INSERT INTO trading_days (trade_date, is_open, previous_open_date, source, observed_at)
            VALUES (?, ?, ?, ?, ?)
            ON CONFLICT(trade_date) DO UPDATE SET
                is_open = excluded.is_open,
                previous_open_date = excluded.previous_open_date,
                source = excluded.source,
                observed_at = excluded.observed_at
            """,
            [
                (
                    _display(str(row.get("cal_date") or "")),
                    1 if int(row.get("is_open") or 0) == 1 else 0,
                    _display(str(row.get("pretrade_date") or "")) or None,
                    source,
                    observed_at,
                )
                for row in rows
                if _display(str(row.get("cal_date") or ""))
            ],
        )

    def replace_stocks(
        self,
        connection: sqlite3.Connection,
        rows: tuple[dict[str, Any], ...],
        source: str,
        observed_at: str,
    ) -> None:
        connection.executemany(
            """
            INSERT INTO market_entities (
                entity_type, identifier, code, name, search_key,
                sector, active, source, observed_at
            )
            VALUES ('stock', ?, ?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(entity_type, identifier) DO UPDATE SET
                code = excluded.code,
                name = excluded.name,
                search_key = excluded.search_key,
                sector = excluded.sector,
                active = excluded.active,
                source = excluded.source,
                observed_at = excluded.observed_at
            """,
            [
                (
                    str(row.get("ts_code") or "").upper(),
                    str(row.get("symbol") or ""),
                    str(row.get("name") or "").strip(),
                    _search_key(row),
                    str(row.get("industry") or "").strip() or None,
                    0 if row.get("list_status") == "D" else 1,
                    source,
                    observed_at,
                )
                for row in rows
                if row.get("ts_code") and row.get("symbol") and row.get("name")
            ],
        )

    def replace_themes(
        self,
        connection: sqlite3.Connection,
        rows: list[dict[str, Any]],
        source: str,
        observed_at: str,
    ) -> None:
        connection.executemany(
            """
            INSERT INTO market_entities (
                entity_type, identifier, code, name, search_key,
                sector, active, source, observed_at
            ) VALUES ('theme', ?, ?, ?, ?, NULL, 1, ?, ?)
            ON CONFLICT(entity_type, identifier) DO UPDATE SET
                code = excluded.code,
                name = excluded.name,
                search_key = excluded.search_key,
                active = 1,
                source = excluded.source,
                observed_at = excluded.observed_at
            """,
            [
                (
                    str(row.get("code") or "").upper(),
                    str(row.get("code") or "").split(".")[0],
                    str(row.get("name") or "").strip(),
                    _normalize(
                        f"{row.get('code') or ''} {row.get('name') or ''}"
                    ),
                    source,
                    observed_at,
                )
                for row in rows
                if row.get("code") and row.get("name")
            ],
        )

    def search(
        self, connection: sqlite3.Connection, query: str, limit: int = 32
    ) -> tuple[MarketEntity, ...]:
        normalized = _normalize(query)
        if not normalized:
            return ()
        rows = connection.execute(
            """
            SELECT entity_type, identifier, code, name, sector
            FROM market_entities
            WHERE active = 1 AND search_key LIKE ?
            ORDER BY
                CASE WHEN code = ? THEN 0 WHEN name = ? THEN 1 WHEN code LIKE ? THEN 2 ELSE 3 END,
                entity_type, name
            LIMIT ?
            """,
            (f"%{normalized}%", normalized, query.strip(), f"{normalized}%", limit),
        ).fetchall()
        return tuple(MarketEntity(**dict(row)) for row in rows)

    def entity(
        self, connection: sqlite3.Connection, entity_type: str, identifier: str
    ) -> MarketEntity | None:
        row = connection.execute(
            """
            SELECT entity_type, identifier, code, name, sector
            FROM market_entities WHERE entity_type = ? AND identifier = ? AND active = 1
            """,
            (entity_type, identifier),
        ).fetchone()
        return MarketEntity(**dict(row)) if row else None

    def open_dates(
        self, connection: sqlite3.Connection, through: str, limit: int = 12
    ) -> tuple[str, ...]:
        return tuple(
            str(row["trade_date"])
            for row in connection.execute(
                """
                SELECT trade_date FROM trading_days
                WHERE is_open = 1 AND trade_date <= ?
                ORDER BY trade_date DESC LIMIT ?
                """,
                (through, limit),
            )
        )

    def open_dates_between(
        self, connection: sqlite3.Connection, start_date: str, end_date: str
    ) -> tuple[str, ...]:
        return tuple(
            str(row["trade_date"])
            for row in connection.execute(
                """
                SELECT trade_date FROM trading_days
                WHERE is_open = 1 AND trade_date BETWEEN ? AND ?
                ORDER BY trade_date
                """,
                (start_date, end_date),
            ).fetchall()
        )

    def active_stock_count(self, connection: sqlite3.Connection) -> int:
        row = connection.execute(
            """
            SELECT COUNT(*) AS count FROM market_entities
            WHERE entity_type = 'stock' AND active = 1
            """
        ).fetchone()
        return int(row["count"] if row else 0)

    def stock_directory(self, connection: sqlite3.Connection) -> tuple[sqlite3.Row, ...]:
        return tuple(
            connection.execute(
                """
                SELECT identifier, code, name, sector FROM market_entities
                WHERE entity_type = 'stock' AND active = 1
                """
            ).fetchall()
        )

    def save_summary(
        self,
        connection: sqlite3.Connection,
        *,
        trade_date: str,
        observed_at: str,
        state: str,
        source: str,
        coverage: float,
        payload: dict[str, Any],
    ) -> None:
        connection.execute(
            """
            INSERT INTO market_summaries
                (trade_date, observed_at, state, source, coverage, payload_json, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(trade_date) DO UPDATE SET
                observed_at = excluded.observed_at,
                state = excluded.state,
                source = excluded.source,
                coverage = excluded.coverage,
                payload_json = excluded.payload_json,
                created_at = excluded.created_at
            """,
            (
                trade_date,
                observed_at,
                state,
                source,
                coverage,
                json.dumps(payload, ensure_ascii=False, separators=(",", ":")),
                datetime.now().astimezone().isoformat(timespec="seconds"),
            ),
        )

    def summaries(
        self, connection: sqlite3.Connection, through: str, limit: int = 260
    ) -> tuple[sqlite3.Row, ...]:
        rows = connection.execute(
            """
            SELECT * FROM market_summaries WHERE trade_date <= ?
            ORDER BY trade_date DESC LIMIT ?
            """,
            (through, limit),
        ).fetchall()
        return tuple(reversed(rows))

    def latest_summary(self, connection: sqlite3.Connection, through: str) -> sqlite3.Row | None:
        return connection.execute(
            "SELECT * FROM market_summaries WHERE trade_date <= ? ORDER BY trade_date DESC LIMIT 1",
            (through,),
        ).fetchone()

    def save_event_revision(
        self,
        connection: sqlite3.Connection,
        *,
        trade_date: str,
        identifier: str,
        event_type: str,
        reason: str,
        first_time: str,
        last_time: str,
        open_times: int | None,
        source: str,
        priority: int,
        created_by: int | None,
        created_at: str,
    ) -> int:
        cursor = connection.execute(
            """
            INSERT INTO market_event_revisions (
                trade_date, identifier, event_type, reason, first_time, last_time,
                open_times, source, priority, created_by, created_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                trade_date,
                identifier,
                event_type,
                reason,
                first_time,
                last_time,
                open_times,
                source,
                priority,
                created_by,
                created_at,
            ),
        )
        return int(cursor.lastrowid)

    def event_revisions(
        self, connection: sqlite3.Connection, trade_date: str
    ) -> tuple[sqlite3.Row, ...]:
        return tuple(
            connection.execute(
                """
                SELECT * FROM (
                    SELECT revisions.*,
                        ROW_NUMBER() OVER (
                            PARTITION BY identifier, event_type
                            ORDER BY priority DESC, id DESC
                        ) AS rank
                    FROM market_event_revisions AS revisions
                    WHERE trade_date = ?
                ) WHERE rank = 1
                """,
                (trade_date,),
            ).fetchall()
        )

    def event_revision_history(
        self, connection: sqlite3.Connection, trade_date: str, identifier: str
    ) -> tuple[sqlite3.Row, ...]:
        return tuple(
            connection.execute(
                """
                SELECT revisions.*, users.username AS created_by_name
                FROM market_event_revisions AS revisions
                LEFT JOIN users ON users.id = revisions.created_by
                WHERE revisions.trade_date = ? AND revisions.identifier = ?
                ORDER BY revisions.id DESC
                """,
                (trade_date, identifier),
            ).fetchall()
        )

    def sector_members(
        self, connection: sqlite3.Connection, trade_date: str, sector_name: str
    ) -> sqlite3.Row | None:
        return connection.execute(
            """
            SELECT * FROM sector_member_snapshots
            WHERE trade_date = ? AND sector_name = ?
            """,
            (trade_date, sector_name),
        ).fetchone()

    def save_sector_members(
        self,
        connection: sqlite3.Connection,
        *,
        trade_date: str,
        sector_name: str,
        sector_code: str,
        observed_at: str,
        source: str,
        coverage: float,
        payload: dict[str, Any],
    ) -> None:
        connection.execute(
            """
            INSERT INTO sector_member_snapshots (
                trade_date, sector_name, sector_code, observed_at, source, coverage, payload_json
            ) VALUES (?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(trade_date, sector_name) DO UPDATE SET
                sector_code = excluded.sector_code,
                observed_at = excluded.observed_at,
                source = excluded.source,
                coverage = excluded.coverage,
                payload_json = excluded.payload_json
            """,
            (
                trade_date,
                sector_name,
                sector_code,
                observed_at,
                source,
                coverage,
                json.dumps(payload, ensure_ascii=False, separators=(",", ":")),
            ),
        )

    def insight_snapshot(
        self,
        connection: sqlite3.Connection,
        kind: str,
        trade_date: str,
        entity_key: str = "",
    ) -> sqlite3.Row | None:
        return connection.execute(
            """
            SELECT * FROM market_insight_snapshots
            WHERE kind = ? AND trade_date = ? AND entity_key = ?
            """,
            (kind, trade_date, entity_key),
        ).fetchone()

    def latest_insight_snapshot(
        self,
        connection: sqlite3.Connection,
        kind: str,
        through: str,
        entity_key: str = "",
    ) -> sqlite3.Row | None:
        return connection.execute(
            """
            SELECT * FROM market_insight_snapshots
            WHERE kind = ? AND trade_date <= ? AND entity_key = ?
            ORDER BY trade_date DESC LIMIT 1
            """,
            (kind, through, entity_key),
        ).fetchone()

    def insight_snapshots(
        self, connection: sqlite3.Connection, kind: str, through: str, limit: int
    ) -> tuple[sqlite3.Row, ...]:
        rows = connection.execute(
            """
            SELECT * FROM market_insight_snapshots
            WHERE kind = ? AND trade_date <= ? AND entity_key = ''
            ORDER BY trade_date DESC LIMIT ?
            """,
            (kind, through, limit),
        ).fetchall()
        return tuple(reversed(rows))

    def save_insight_snapshot(
        self,
        connection: sqlite3.Connection,
        *,
        kind: str,
        trade_date: str,
        entity_key: str,
        observed_at: str,
        state: str,
        source: str,
        coverage: float,
        payload: dict[str, Any],
    ) -> None:
        connection.execute(
            """
            INSERT INTO market_insight_snapshots (
                kind, trade_date, entity_key, observed_at, state, source, coverage, payload_json
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(kind, trade_date, entity_key) DO UPDATE SET
                observed_at = excluded.observed_at,
                state = excluded.state,
                source = excluded.source,
                coverage = excluded.coverage,
                payload_json = excluded.payload_json
            """,
            (
                kind,
                trade_date,
                entity_key,
                observed_at,
                state,
                source,
                coverage,
                json.dumps(payload, ensure_ascii=False, separators=(",", ":")),
            ),
        )

    def seat_aliases(self, connection: sqlite3.Connection) -> dict[str, str]:
        return {
            str(row["seat_name"]): str(row["alias_name"])
            for row in connection.execute(
                "SELECT seat_name, alias_name FROM seat_aliases ORDER BY seat_name"
            ).fetchall()
        }

    def save_seat_alias(
        self,
        connection: sqlite3.Connection,
        seat_name: str,
        alias_name: str,
        updated_at: str,
        updated_by: int,
    ) -> None:
        connection.execute(
            """
            INSERT INTO seat_aliases (seat_name, alias_name, updated_at, updated_by)
            VALUES (?, ?, ?, ?)
            ON CONFLICT(seat_name) DO UPDATE SET
                alias_name = excluded.alias_name,
                updated_at = excluded.updated_at,
                updated_by = excluded.updated_by
            """,
            (seat_name, alias_name, updated_at, updated_by),
        )

    def save_chart(
        self,
        connection: sqlite3.Connection,
        *,
        entity_type: str,
        identifier: str,
        interval: str,
        trade_date: str,
        observed_at: str,
        source: str,
        usage: str,
        adjustment: str,
        coverage: float,
        payload: dict[str, Any],
    ) -> None:
        connection.execute(
            """
            INSERT INTO chart_series
                (entity_type, identifier, interval, trade_date, observed_at, source, usage,
                 adjustment, coverage, payload_json, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(entity_type, identifier, interval, trade_date) DO UPDATE SET
                observed_at = excluded.observed_at,
                source = excluded.source,
                usage = excluded.usage,
                adjustment = excluded.adjustment,
                coverage = excluded.coverage,
                payload_json = excluded.payload_json,
                created_at = excluded.created_at
            """,
            (
                entity_type,
                identifier,
                interval,
                trade_date,
                observed_at,
                source,
                usage,
                adjustment,
                coverage,
                json.dumps(payload, ensure_ascii=False, separators=(",", ":")),
                datetime.now().astimezone().isoformat(timespec="seconds"),
            ),
        )

    def chart(
        self, connection: sqlite3.Connection, entity_type: str, identifier: str, interval: str
    ) -> sqlite3.Row | None:
        return connection.execute(
            """
            SELECT * FROM chart_series
            WHERE entity_type = ? AND identifier = ? AND interval = ?
            ORDER BY trade_date DESC LIMIT 1
            """,
            (entity_type, identifier, interval),
        ).fetchone()


def _display(value: str) -> str:
    compact = value.replace("-", "")
    if len(compact) != 8 or not compact.isdigit():
        return ""
    return f"{compact[:4]}-{compact[4:6]}-{compact[6:]}"


def _normalize(value: str) -> str:
    return re.sub(r"\s+", "", value).casefold()


def _search_key(row: dict[str, Any]) -> str:
    return " ".join(
        filter(
            None,
            (
                _normalize(str(row.get("symbol") or "")),
                _normalize(str(row.get("ts_code") or "")),
                _normalize(str(row.get("name") or "")),
                _normalize(str(row.get("industry") or "")),
            ),
        )
    )
