"""Database adapter abstraction for cross-database support."""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any

from .dialect import BigQueryDialect, Dialect, PostgresDialect


class DbAdapter(ABC):
    """Abstract database adapter interface."""

    @property
    @abstractmethod
    def db_type(self) -> str:
        """Database type identifier (e.g., 'postgres', 'bigquery')."""

    @property
    @abstractmethod
    def dialect(self) -> Dialect:
        """SQL dialect for this database."""

    @abstractmethod
    async def connect(self) -> None: ...

    @abstractmethod
    async def close(self) -> None: ...

    @abstractmethod
    async def fetch(self, sql: str, params: list | None = None) -> list[dict]: ...

    @abstractmethod
    async def fetchval(self, sql: str, params: list | None = None) -> Any: ...

    @abstractmethod
    async def list_tables(self) -> list[dict]: ...

    @abstractmethod
    async def list_columns(self, schema: str, table: str) -> list[dict]: ...

    @abstractmethod
    async def get_column_names(self, schema: str, table: str) -> list[str]: ...

    @abstractmethod
    async def health_check(self) -> bool: ...


class PostgresAdapter(DbAdapter):
    """PostgreSQL adapter using asyncpg."""

    def __init__(self, database_url: str):
        self._url = database_url
        self._pool = None
        self._dialect = PostgresDialect()

    @property
    def db_type(self) -> str:
        return "postgres"

    @property
    def dialect(self) -> Dialect:
        return self._dialect

    async def connect(self):
        import asyncpg

        self._pool = await asyncpg.create_pool(self._url, min_size=1, max_size=5)

    async def close(self):
        if self._pool:
            await self._pool.close()

    async def fetch(self, sql, params=None):
        async with self._pool.acquire() as conn:
            rows = await conn.fetch(sql, *(params or []))
            return [dict(r) for r in rows]

    async def fetchval(self, sql, params=None):
        async with self._pool.acquire() as conn:
            return await conn.fetchval(sql, *(params or []))

    async def list_tables(self):
        rows = await self.fetch(
            "SELECT table_schema, table_name FROM information_schema.tables "
            "WHERE table_schema NOT IN ('pg_catalog', 'information_schema') "
            "ORDER BY table_schema, table_name"
        )
        return [{"schema": r["table_schema"], "name": r["table_name"]} for r in rows]

    async def list_columns(self, schema, table):
        rows = await self.fetch(
            "SELECT column_name, data_type, is_nullable "
            "FROM information_schema.columns "
            "WHERE table_schema = $1 AND table_name = $2 "
            "ORDER BY ordinal_position",
            [schema, table],
        )
        return [
            {"name": r["column_name"], "type": r["data_type"], "nullable": r["is_nullable"] == "YES"}
            for r in rows
        ]

    async def get_column_names(self, schema, table):
        rows = await self.fetch(
            "SELECT column_name FROM information_schema.columns "
            "WHERE table_schema = $1 AND table_name = $2",
            [schema, table],
        )
        return [r["column_name"] for r in rows]

    async def health_check(self):
        try:
            await self.fetchval("SELECT 1")
            return True
        except Exception:
            return False


class BigQueryAdapter(DbAdapter):
    """BigQuery adapter using google-cloud-bigquery."""

    def __init__(self, project: str, dataset: str, credentials_path: str | None = None):
        self._project = project
        self._dataset = dataset
        self._credentials_path = credentials_path
        self._client = None
        self._dialect = BigQueryDialect()

    @property
    def db_type(self) -> str:
        return "bigquery"

    @property
    def dialect(self) -> Dialect:
        return self._dialect

    async def connect(self):
        import asyncio

        try:
            from google.cloud import bigquery
        except ImportError:
            raise ImportError(
                "google-cloud-bigquery is required for BigQuery support. "
                "Install with: pip install 'mdash[bigquery]'"
            )

        def _connect():
            kwargs = {"project": self._project}
            if self._credentials_path:
                from google.oauth2 import service_account

                kwargs["credentials"] = service_account.Credentials.from_service_account_file(
                    self._credentials_path
                )
            return bigquery.Client(**kwargs)

        self._client = await asyncio.to_thread(_connect)

    async def close(self):
        if self._client:
            self._client.close()

    def _make_query_params(self, params: list | None) -> list:
        from google.cloud import bigquery

        bq_params = []
        for i, val in enumerate(params or [], 1):
            if isinstance(val, float):
                bq_params.append(bigquery.ScalarQueryParameter(f"p{i}", "FLOAT64", val))
            elif isinstance(val, int):
                bq_params.append(bigquery.ScalarQueryParameter(f"p{i}", "INT64", val))
            else:
                bq_params.append(bigquery.ScalarQueryParameter(f"p{i}", "STRING", str(val)))
        return bq_params

    async def fetch(self, sql, params=None):
        import asyncio

        from google.cloud import bigquery

        bq_params = self._make_query_params(params)

        def _fetch():
            job_config = bigquery.QueryJobConfig(query_parameters=bq_params) if bq_params else None
            results = self._client.query(sql, job_config=job_config).result()
            return [dict(row) for row in results]

        return await asyncio.to_thread(_fetch)

    async def fetchval(self, sql, params=None):
        rows = await self.fetch(sql, params)
        if rows:
            return list(rows[0].values())[0]
        return None

    async def list_tables(self):
        rows = await self.fetch(
            f"SELECT table_name FROM "
            f"`{self._project}.{self._dataset}.INFORMATION_SCHEMA.TABLES` "
            f"ORDER BY table_name"
        )
        return [{"schema": self._dataset, "name": r["table_name"]} for r in rows]

    async def list_columns(self, schema, table):
        rows = await self.fetch(
            f"SELECT column_name, data_type, is_nullable FROM "
            f"`{self._project}.{self._dataset}.INFORMATION_SCHEMA.COLUMNS` "
            f"WHERE table_name = @p1 "
            f"ORDER BY ordinal_position",
            [table],
        )
        return [
            {"name": r["column_name"], "type": r["data_type"], "nullable": r["is_nullable"] == "YES"}
            for r in rows
        ]

    async def get_column_names(self, schema, table):
        cols = await self.list_columns(schema, table)
        return [c["name"] for c in cols]

    async def health_check(self):
        try:
            await self.fetchval("SELECT 1")
            return True
        except Exception:
            return False
