import click
import logging
import sqlite3

from pathlib import Path
from sqlite3 import Connection
from typing import Any, NamedTuple

from zet.config import get_cache_db_path


logger = logging.getLogger(__name__)


def get_conn():
    path = get_cache_db_path()
    conn = sqlite3.connect(path, autocommit=False)
    conn.row_factory = sqlite3.Row  # Allows name-based access to columns
    migrate(conn)
    return conn


def select(conn: Connection, query: str, *params: Any) -> list[sqlite3.Row]:
    cursor = conn.execute(query, params)
    return cursor.fetchall()


def select_one(conn: Connection, query: str, params: list[Any]) -> sqlite3.Row:
    cursor = conn.execute(query, params)
    return cursor.fetchone()


def table_exists(conn: Connection, table_name: str):
    query = """
        SELECT count(*) AS count
        FROM sqlite_master
        WHERE type='table' AND name=?
    """
    row = select_one(conn, query, [table_name])
    return row["count"] > 0


def migrate(conn: Connection):
    migrations_dir = Path(__file__).parent / "migrations"
    applied_migrations = _get_applied_migrations(conn)
    available_migrations = [
        path for path in migrations_dir.iterdir() if path.is_file and path.suffix == ".sql"
    ]

    try:
        for path in available_migrations:
            if path.name not in applied_migrations:
                _apply_migration(conn, path)
                conn.commit()
    except Exception:
        logger.exception("Failed applying migrations")
        raise click.ClickException("Failed applying migrations")


def _get_applied_migrations(conn: Connection) -> list[str]:
    if table_exists(conn, "migrations"):
        rows = select(conn, "SELECT name FROM migrations ORDER BY name ASC")
        return [r["name"] for r in rows]
    else:
        return []


def _apply_migration(conn: Connection, path: Path):
    logger.info(f"Applying migration: {path.name}")

    with path.open("r") as f:
        sql = f.read()

    with conn:
        conn.executescript(sql)
        conn.execute(
            """
            INSERT INTO migrations(name, applied_at)
            VALUES (?, datetime('now'))
            """,
            [path.name],
        )


class Point(NamedTuple):
    lat: float
    lon: float


def get_lines(
    min_lat: float,
    min_lon: float,
    max_lat: float,
    max_lon: float,
) -> list[tuple[Point, Point]]:
    conn = get_conn()
    params = {"min_lat": min_lat, "min_lon": min_lon, "max_lat": max_lat, "max_lon": max_lon}
    cursor = conn.execute(
        """
        SELECT lat1, lon1, lat2, lon2
        FROM map_lines
        WHERE (lat1 >= :min_lat AND lat1 <= :max_lat AND lon1 >= :min_lon AND lon1 <= :max_lon)
           OR (lat2 >= :min_lat AND lat2 <= :max_lat AND lon2 >= :min_lon AND lon2 <= :max_lon)
        """,
        params,
    )
    lines = cursor.fetchall()
    conn.commit()

    return [
        (
            Point(line["lat1"], line["lon1"]),
            Point(line["lat2"], line["lon2"]),
        )
        for line in lines
    ]


def get_all_lines() -> list[tuple[Point, Point]]:
    conn = get_conn()
    cursor = conn.execute("SELECT lat1, lon1, lat2, lon2 FROM map_lines")
    lines = cursor.fetchall()
    conn.commit()

    return [
        (
            Point(line["lat1"], line["lon1"]),
            Point(line["lat2"], line["lon2"]),
        )
        for line in lines
    ]
