# services/provider_tracking.py
from __future__ import annotations

import asyncio
import json
import sqlite3
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Optional


@dataclass(slots=True)
class ProviderAttempt:
    order_id: int
    provider: str
    operation: str
    state: str
    market_order_id: Optional[str]
    wallet_seqno_before: Optional[int]
    signed_transfer_hash: Optional[str]
    last_error: Optional[str]
    meta: dict[str, Any]
    request_started_at: Optional[str]
    response_received_at: Optional[str]
    created_at: str
    updated_at: str


class ProviderTrackingStore:
    """
    Durable tracking + idempotency for provider purchase attempts.

    One order may start the MarketApp BUY operation only once.
    Ambiguous network failures are intentionally not retried.
    """

    def __init__(
        self,
        db_path: str | Path = "matrix_bot.db",
    ) -> None:
        self.db_path = str(db_path)

    def _connect(self) -> sqlite3.Connection:
        conn = sqlite3.connect(
            self.db_path,
            timeout=30,
            check_same_thread=False,
        )
        conn.row_factory = sqlite3.Row
        conn.execute("PRAGMA busy_timeout = 30000")
        conn.execute("PRAGMA foreign_keys = ON")
        return conn

    async def ensure_schema(self) -> None:
        await asyncio.to_thread(
            self._ensure_schema_sync
        )

    def _ensure_schema_sync(self) -> None:
        conn = self._connect()
        try:
            conn.executescript(
                """
                CREATE TABLE IF NOT EXISTS provider_attempts (
                    order_id INTEGER PRIMARY KEY,
                    provider TEXT NOT NULL,
                    operation TEXT NOT NULL,
                    state TEXT NOT NULL,
                    market_order_id TEXT,
                    wallet_seqno_before INTEGER,
                    signed_transfer_hash TEXT,
                    last_error TEXT,
                    meta_json TEXT NOT NULL DEFAULT '{}',
                    request_started_at TIMESTAMP,
                    response_received_at TIMESTAMP,
                    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                    FOREIGN KEY(order_id)
                        REFERENCES orders(id)
                        ON DELETE CASCADE
                );

                CREATE TABLE IF NOT EXISTS provider_events (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    order_id INTEGER NOT NULL,
                    event TEXT NOT NULL,
                    detail TEXT,
                    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                    FOREIGN KEY(order_id)
                        REFERENCES orders(id)
                        ON DELETE CASCADE
                );

                CREATE INDEX IF NOT EXISTS
                idx_provider_attempts_state
                ON provider_attempts(state);

                CREATE INDEX IF NOT EXISTS
                idx_provider_events_order_id
                ON provider_events(order_id);
                """
            )
            conn.commit()
        finally:
            conn.close()

    async def begin_buy_once(
        self,
        order_id: int,
        *,
        operation: str,
    ) -> bool:
        await self.ensure_schema()
        return await asyncio.to_thread(
            self._begin_buy_once_sync,
            int(order_id),
            str(operation),
        )

    def _begin_buy_once_sync(
        self,
        order_id: int,
        operation: str,
    ) -> bool:
        conn = self._connect()
        try:
            conn.execute("BEGIN IMMEDIATE")

            row = conn.execute(
                """
                SELECT order_id
                FROM provider_attempts
                WHERE order_id = ?
                LIMIT 1
                """,
                (order_id,),
            ).fetchone()

            if row:
                conn.rollback()
                return False

            conn.execute(
                """
                INSERT INTO provider_attempts (
                    order_id,
                    provider,
                    operation,
                    state
                )
                VALUES (?, 'marketapp', ?, 'prepared')
                """,
                (order_id, operation),
            )
            conn.execute(
                """
                INSERT INTO provider_events (
                    order_id, event, detail
                )
                VALUES (?, 'prepared', ?)
                """,
                (
                    order_id,
                    f"MarketApp {operation} prepared",
                ),
            )
            conn.commit()
            return True
        except Exception:
            conn.rollback()
            raise
        finally:
            conn.close()

    async def mark_request_started(
        self,
        order_id: int,
    ) -> None:
        await self._update(
            order_id,
            state="request_started",
            request_started=True,
            event="marketapp_request_started",
        )

    async def mark_response_received(
        self,
        order_id: int,
        *,
        market_order_id: Optional[str] = None,
        meta: Optional[dict[str, Any]] = None,
    ) -> None:
        await self._update(
            order_id,
            state="provider_response_received",
            market_order_id=market_order_id,
            meta=meta,
            response_received=True,
            event="marketapp_response_received",
        )

    async def mark_wallet_submission(
        self,
        order_id: int,
        *,
        seqno_before: Optional[int],
        signed_transfer_hash: Optional[str],
        state: str,
        error: Optional[str] = None,
    ) -> None:
        await self._update(
            order_id,
            state=state,
            wallet_seqno_before=seqno_before,
            signed_transfer_hash=signed_transfer_hash,
            error=error,
            event=state,
        )

    async def mark_terminal(
        self,
        order_id: int,
        *,
        state: str,
        error: Optional[str] = None,
    ) -> None:
        await self._update(
            order_id,
            state=state,
            error=error,
            event=state,
        )

    async def _update(
        self,
        order_id: int,
        *,
        state: Optional[str] = None,
        market_order_id: Optional[str] = None,
        wallet_seqno_before: Optional[int] = None,
        signed_transfer_hash: Optional[str] = None,
        error: Optional[str] = None,
        meta: Optional[dict[str, Any]] = None,
        request_started: bool = False,
        response_received: bool = False,
        event: Optional[str] = None,
    ) -> None:
        await self.ensure_schema()
        await asyncio.to_thread(
            self._update_sync,
            int(order_id),
            state,
            market_order_id,
            wallet_seqno_before,
            signed_transfer_hash,
            error,
            meta,
            request_started,
            response_received,
            event,
        )

    def _update_sync(
        self,
        order_id: int,
        state: Optional[str],
        market_order_id: Optional[str],
        wallet_seqno_before: Optional[int],
        signed_transfer_hash: Optional[str],
        error: Optional[str],
        meta: Optional[dict[str, Any]],
        request_started: bool,
        response_received: bool,
        event: Optional[str],
    ) -> None:
        conn = self._connect()
        try:
            fields = ["updated_at = CURRENT_TIMESTAMP"]
            params: list[Any] = []

            if state is not None:
                fields.append("state = ?")
                params.append(str(state))

            if market_order_id is not None:
                fields.append("market_order_id = ?")
                params.append(str(market_order_id)[:300])

            if wallet_seqno_before is not None:
                fields.append("wallet_seqno_before = ?")
                params.append(int(wallet_seqno_before))

            if signed_transfer_hash is not None:
                fields.append("signed_transfer_hash = ?")
                params.append(str(signed_transfer_hash)[:300])

            if error is not None:
                fields.append("last_error = ?")
                params.append(str(error)[:1500])

            if meta is not None:
                fields.append("meta_json = ?")
                params.append(
                    json.dumps(
                        meta,
                        ensure_ascii=False,
                        separators=(",", ":"),
                        default=str,
                    )[:10000]
                )

            if request_started:
                fields.append(
                    "request_started_at = CURRENT_TIMESTAMP"
                )

            if response_received:
                fields.append(
                    "response_received_at = CURRENT_TIMESTAMP"
                )

            params.append(order_id)

            conn.execute(
                f"""
                UPDATE provider_attempts
                SET {", ".join(fields)}
                WHERE order_id = ?
                """,
                tuple(params),
            )

            if event:
                conn.execute(
                    """
                    INSERT INTO provider_events (
                        order_id, event, detail
                    )
                    VALUES (?, ?, ?)
                    """,
                    (
                        order_id,
                        str(event)[:120],
                        (
                            str(error)[:1500]
                            if error
                            else None
                        ),
                    ),
                )

            conn.commit()
        finally:
            conn.close()

    async def get_attempt(
        self,
        order_id: int,
    ) -> Optional[ProviderAttempt]:
        await self.ensure_schema()
        return await asyncio.to_thread(
            self._get_attempt_sync,
            int(order_id),
        )

    def _get_attempt_sync(
        self,
        order_id: int,
    ) -> Optional[ProviderAttempt]:
        conn = self._connect()
        try:
            row = conn.execute(
                """
                SELECT *
                FROM provider_attempts
                WHERE order_id = ?
                LIMIT 1
                """,
                (order_id,),
            ).fetchone()

            if not row:
                return None

            try:
                meta = json.loads(
                    row["meta_json"] or "{}"
                )
            except Exception:
                meta = {}

            return ProviderAttempt(
                order_id=int(row["order_id"]),
                provider=str(row["provider"]),
                operation=str(row["operation"]),
                state=str(row["state"]),
                market_order_id=(
                    str(row["market_order_id"])
                    if row["market_order_id"] is not None
                    else None
                ),
                wallet_seqno_before=(
                    int(row["wallet_seqno_before"])
                    if row["wallet_seqno_before"] is not None
                    else None
                ),
                signed_transfer_hash=(
                    str(row["signed_transfer_hash"])
                    if row["signed_transfer_hash"] is not None
                    else None
                ),
                last_error=(
                    str(row["last_error"])
                    if row["last_error"] is not None
                    else None
                ),
                meta=meta if isinstance(meta, dict) else {},
                request_started_at=(
                    str(row["request_started_at"])
                    if row["request_started_at"] is not None
                    else None
                ),
                response_received_at=(
                    str(row["response_received_at"])
                    if row["response_received_at"] is not None
                    else None
                ),
                created_at=str(row["created_at"] or ""),
                updated_at=str(row["updated_at"] or ""),
            )
        finally:
            conn.close()


__all__ = [
    "ProviderTrackingStore",
    "ProviderAttempt",
]