from __future__ import annotations

import json
import os
import sqlite3
import threading
from datetime import datetime, timedelta, timezone
from typing import Optional

from app.config import settings
from app.models.event import EventStatus, EventType, OutboundEvent


class EventStore:
    def __init__(self, db_path: str):
        self.db_path = db_path
        os.makedirs(os.path.dirname(os.path.abspath(db_path)), exist_ok=True)
        self._local = threading.local()
        self._init_db()

    def _now(self) -> datetime:
        return datetime.now(timezone.utc).replace(tzinfo=None)

    def _connection(self) -> sqlite3.Connection:
        if not hasattr(self._local, "conn") or self._local.conn is None:
            self._local.conn = sqlite3.connect(self.db_path, check_same_thread=False)
            self._local.conn.row_factory = sqlite3.Row
        return self._local.conn

    def _init_db(self) -> None:
        with sqlite3.connect(self.db_path) as conn:
            conn.execute(
                """
                CREATE TABLE IF NOT EXISTS outbound_events (
                    id TEXT PRIMARY KEY,
                    event_type TEXT NOT NULL,
                    payload TEXT NOT NULL,
                    status TEXT NOT NULL DEFAULT 'pending',
                    attempt_count INTEGER NOT NULL DEFAULT 0,
                    next_retry_at TIMESTAMP NOT NULL,
                    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                    last_error TEXT
                )
                """
            )
            conn.execute(
                "CREATE INDEX IF NOT EXISTS idx_status_next_retry ON outbound_events(status, next_retry_at)"
            )
            conn.execute(
                "CREATE INDEX IF NOT EXISTS idx_created_at ON outbound_events(created_at)"
            )

    def save(self, event: OutboundEvent) -> None:
        now = self._now().isoformat()
        with self._connection() as conn:
            conn.execute(
                """
                INSERT INTO outbound_events
                (id, event_type, payload, status, attempt_count, next_retry_at, created_at, updated_at, last_error)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    event.event_id,
                    event.event_type.value,
                    json.dumps(event.to_payload(), default=str),
                    EventStatus.PENDING.value,
                    0,
                    now,
                    now,
                    now,
                    None,
                ),
            )

    def fetch_pending(self, limit: int = 100) -> list[OutboundEvent]:
        now = self._now().isoformat()
        cursor = self._connection().execute(
            """
            SELECT id, event_type, payload, status, attempt_count
            FROM outbound_events
            WHERE status IN ('pending', 'retrying') AND next_retry_at <= ?
            ORDER BY next_retry_at ASC
            LIMIT ?
            """,
            (now, limit),
        )
        return [self._row_to_event(row) for row in cursor.fetchall()]

    def _row_to_event(self, row: sqlite3.Row) -> OutboundEvent:
        payload = json.loads(row["payload"])
        return OutboundEvent(
            event_id=payload["event_id"],
            event_type=EventType(payload["event_type"]),
            account_id=payload["account_id"],
            tenant_code=payload["tenant_code"],
            data=payload["data"],
            status=EventStatus(row["status"]),
            attempt_count=row["attempt_count"],
        )

    def get(self, event_id: str) -> Optional[OutboundEvent]:
        row = self._connection().execute(
            "SELECT id, event_type, payload, status, attempt_count FROM outbound_events WHERE id = ?",
            (event_id,),
        ).fetchone()
        if row is None:
            return None
        return self._row_to_event(row)

    def mark_delivered(self, event_id: str) -> None:
        now = self._now().isoformat()
        with self._connection() as conn:
            conn.execute(
                "UPDATE outbound_events SET status = 'delivered', updated_at = ? WHERE id = ?",
                (now, event_id),
            )

    def mark_retry(self, event_id: str, status: EventStatus, attempt_count: int, next_retry_at: datetime, last_error: Optional[str]) -> None:
        now = self._now().isoformat()
        with self._connection() as conn:
            conn.execute(
                """
                UPDATE outbound_events
                SET status = ?, attempt_count = ?, next_retry_at = ?, updated_at = ?, last_error = ?
                WHERE id = ?
                """,
                (status.value, attempt_count, next_retry_at.isoformat(), now, last_error, event_id),
            )

    def cleanup(self, retention_days: int) -> int:
        threshold = (self._now() - timedelta(days=retention_days)).isoformat()
        with self._connection() as conn:
            cursor = conn.execute(
                """
                DELETE FROM outbound_events
                WHERE status IN ('delivered', 'dead_letter') AND created_at < ?
                """,
                (threshold,),
            )
            return cursor.rowcount


event_store = EventStore(settings.event_store_path)
