from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any

import duckdb

from awspipe.providers.base import TableProvider


CACHE_SCHEMA = "awspipe_cache"
INTERNAL_COLUMNS = (
    ("_profile", "VARCHAR"),
    ("_source_region", "VARCHAR"),
)


@dataclass(frozen=True)
class QueryResult:
    columns: list[str]
    rows: list[tuple[Any, ...]]


class BackendAdapter:
    def __init__(self, db_path: Path) -> None:
        self.db_path = db_path
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._bootstrap()

    def _connect(self) -> duckdb.DuckDBPyConnection:
        return duckdb.connect(str(self.db_path))

    def _bootstrap(self) -> None:
        with self._connect() as conn:
            conn.execute(f"CREATE SCHEMA IF NOT EXISTS {CACHE_SCHEMA}")
            conn.execute(
                f"""
                CREATE TABLE IF NOT EXISTS {CACHE_SCHEMA}.cache_metadata (
                    table_name VARCHAR,
                    profile_name VARCHAR,
                    source_region VARCHAR,
                    fetched_at TIMESTAMP,
                    row_count BIGINT,
                    PRIMARY KEY (table_name, profile_name, source_region)
                )
                """
            )

    def ensure_provider(self, provider: TableProvider) -> None:
        table_definition = ", ".join(
            [f"{name} {duck_type}" for name, duck_type in INTERNAL_COLUMNS]
            + [f"{column.name} {column.duckdb_type}" for column in provider.columns]
        )
        view_columns = ", ".join(column.name for column in provider.columns)
        with self._connect() as conn:
            conn.execute(f"CREATE TABLE IF NOT EXISTS {CACHE_SCHEMA}.{provider.name}__data ({table_definition})")
            conn.execute(
                f"""
                CREATE OR REPLACE VIEW {provider.name} AS
                SELECT {view_columns}
                FROM {CACHE_SCHEMA}.{provider.name}__data
                """
            )

    def is_fresh(self, provider: TableProvider, profile: str | None, source_region: str, ttl_seconds: int) -> bool:
        profile_name = profile or "default"
        with self._connect() as conn:
            row = conn.execute(
                f"""
                SELECT fetched_at
                FROM {CACHE_SCHEMA}.cache_metadata
                WHERE table_name = ? AND profile_name = ? AND source_region = ?
                """,
                [provider.name, profile_name, source_region],
            ).fetchone()
        if row is None or row[0] is None:
            return False
        fetched_at = row[0]
        if fetched_at.tzinfo is None:
            fetched_at = fetched_at.replace(tzinfo=timezone.utc)
        return fetched_at >= datetime.now(timezone.utc) - timedelta(seconds=ttl_seconds)

    def replace_rows(
        self,
        provider: TableProvider,
        profile: str | None,
        source_region: str,
        rows: list[dict[str, Any]],
    ) -> None:
        self.ensure_provider(provider)
        profile_name = profile or "default"
        column_names = [name for name, _ in INTERNAL_COLUMNS] + [column.name for column in provider.columns]
        placeholders = ", ".join(["?"] * len(column_names))
        insert_sql = f"INSERT INTO {CACHE_SCHEMA}.{provider.name}__data ({', '.join(column_names)}) VALUES ({placeholders})"
        values = [
            [profile_name, source_region] + [row.get(column.name) for column in provider.columns]
            for row in rows
        ]
        fetched_at = datetime.now(timezone.utc)
        with self._connect() as conn:
            conn.execute(
                f"DELETE FROM {CACHE_SCHEMA}.{provider.name}__data WHERE _profile = ? AND _source_region = ?",
                [profile_name, source_region],
            )
            if values:
                conn.executemany(insert_sql, values)
            conn.execute(
                f"""
                INSERT INTO {CACHE_SCHEMA}.cache_metadata (table_name, profile_name, source_region, fetched_at, row_count)
                VALUES (?, ?, ?, ?, ?)
                ON CONFLICT (table_name, profile_name, source_region)
                DO UPDATE SET fetched_at = excluded.fetched_at, row_count = excluded.row_count
                """,
                [provider.name, profile_name, source_region, fetched_at, len(rows)],
            )

    def execute(self, sql: str) -> QueryResult:
        with self._connect() as conn:
            result = conn.execute(sql)
            return QueryResult(
                columns=[description[0] for description in result.description],
                rows=result.fetchall(),
            )

    def execute_scoped(
        self,
        sql: str,
        *,
        profile: str | None,
        table_names: list[str],
        source_regions: list[str],
    ) -> QueryResult:
        profile_name = profile or "default"
        profile_literal = self._sql_literal(profile_name)
        region_literals = ", ".join(self._sql_literal(region) for region in source_regions)
        internal_column_names = {name for name, _ in INTERNAL_COLUMNS}

        with self._connect() as conn:
            for table_name in table_names:
                columns = conn.execute(
                    f"""
                    SELECT column_name
                    FROM information_schema.columns
                    WHERE table_schema = '{CACHE_SCHEMA}' AND table_name = ?
                    ORDER BY ordinal_position
                    """,
                    [f"{table_name}__data"],
                ).fetchall()
                visible_columns = [
                    column_name
                    for (column_name,) in columns
                    if column_name not in internal_column_names
                ]
                conn.execute(
                    f"""
                    CREATE OR REPLACE TEMP VIEW {table_name} AS
                    SELECT {", ".join(visible_columns)}
                    FROM {CACHE_SCHEMA}.{table_name}__data
                    WHERE _profile = {profile_literal} AND _source_region IN ({region_literals})
                    """
                )

            result = conn.execute(sql)
            return QueryResult(
                columns=[description[0] for description in result.description],
                rows=result.fetchall(),
            )

    @staticmethod
    def _sql_literal(value: str) -> str:
        escaped = value.replace("'", "''")
        return f"'{escaped}'"

    def clear_cache(self) -> None:
        with self._connect() as conn:
            cache_tables = [
                row[0]
                for row in conn.execute(
                    f"SELECT table_name FROM information_schema.tables WHERE table_schema = '{CACHE_SCHEMA}'"
                ).fetchall()
            ]
            main_views = [
                row[0]
                for row in conn.execute(
                    "SELECT table_name FROM information_schema.views WHERE table_schema = 'main'"
                ).fetchall()
            ]
            for table_name in cache_tables:
                conn.execute(f"DROP TABLE IF EXISTS {CACHE_SCHEMA}.{table_name}")
            for view_name in main_views:
                conn.execute(f"DROP VIEW IF EXISTS {view_name}")
        self._bootstrap()
