"""Auth data storage — protocol + default SQL implementation.

Uses sqlite3 (stdlib) for local dev, psycopg2 (optional) for PostgreSQL in production.
Zero required dependencies.
"""

from __future__ import annotations

import os
import sqlite3
import uuid
from datetime import datetime, timezone
from typing import Protocol, runtime_checkable
from urllib.parse import urlparse

from waft_client.models import User


@runtime_checkable
class AuthStore(Protocol):
    """Interface for auth data storage. Implement this to use a custom backend."""

    def get_user(self, user_id: str) -> User | None: ...
    def get_user_by_email(self, email: str) -> User | None: ...
    def get_user_by_provider(self, provider: str, external_id: str) -> User | None: ...
    def create_user(self, email: str) -> User: ...
    def list_users(self) -> list[User]: ...
    def store_credential(
        self, user_id: str, provider: str, external_id: str, data: str
    ) -> None: ...
    def get_credential(self, user_id: str, provider: str) -> tuple[str, str] | None: ...
    def get_credential_by_external(
        self, provider: str, external_id: str
    ) -> tuple[str, str] | None: ...
    def create_invite(self, email: str) -> str: ...
    def has_invite(self, email: str) -> bool: ...
    def use_invite(self, email: str) -> None: ...


class SQLStore:
    """Default SQL-based auth store. Uses sqlite3 or psycopg2 depending on DATABASE_URL."""

    def __init__(self, database_url: str | None = None) -> None:
        url = database_url or os.environ.get("DATABASE_URL", "")
        self._is_pg = url.startswith(("postgresql://", "postgres://"))

        if self._is_pg:
            import psycopg2

            self._connect = lambda: psycopg2.connect(url)
        else:
            path = urlparse(url).path if url and "://" in url else (url or "waft_auth.db")
            self._connect = lambda: sqlite3.connect(path)

        self._ph = "%s" if self._is_pg else "?"
        self._create_tables()

    def _create_tables(self) -> None:
        conn = self._connect()
        try:
            cur = conn.cursor()
            cur.execute(
                """CREATE TABLE IF NOT EXISTS waft_auth_users (
                    id TEXT PRIMARY KEY,
                    email TEXT UNIQUE NOT NULL,
                    created_at TEXT NOT NULL
                )"""
            )
            cur.execute(
                """CREATE TABLE IF NOT EXISTS waft_auth_credentials (
                    id TEXT PRIMARY KEY,
                    user_id TEXT NOT NULL REFERENCES waft_auth_users(id),
                    provider TEXT NOT NULL,
                    external_id TEXT NOT NULL,
                    credential_data TEXT NOT NULL DEFAULT '',
                    created_at TEXT NOT NULL,
                    UNIQUE(provider, external_id)
                )"""
            )
            cur.execute(
                """CREATE TABLE IF NOT EXISTS waft_auth_invites (
                    id TEXT PRIMARY KEY,
                    email TEXT NOT NULL,
                    token TEXT NOT NULL,
                    created_at TEXT NOT NULL,
                    used_at TEXT
                )"""
            )
            conn.commit()
        finally:
            conn.close()

    def _q(self, sql: str) -> str:
        """Replace ? placeholders with %s for PostgreSQL."""
        if self._is_pg:
            return sql.replace("?", "%s")
        return sql

    def get_user(self, user_id: str) -> User | None:
        conn = self._connect()
        try:
            cur = conn.cursor()
            cur.execute(
                self._q("SELECT id, email, created_at FROM waft_auth_users WHERE id = ?"),
                (user_id,),
            )
            row = cur.fetchone()
            return User(id=row[0], email=row[1], created_at=datetime.fromisoformat(row[2])) if row else None
        finally:
            conn.close()

    def get_user_by_email(self, email: str) -> User | None:
        conn = self._connect()
        try:
            cur = conn.cursor()
            cur.execute(
                self._q("SELECT id, email, created_at FROM waft_auth_users WHERE email = ?"),
                (email,),
            )
            row = cur.fetchone()
            return User(id=row[0], email=row[1], created_at=datetime.fromisoformat(row[2])) if row else None
        finally:
            conn.close()

    def get_user_by_provider(self, provider: str, external_id: str) -> User | None:
        conn = self._connect()
        try:
            cur = conn.cursor()
            cur.execute(
                self._q(
                    "SELECT u.id, u.email, u.created_at FROM waft_auth_users u "
                    "JOIN waft_auth_credentials c ON u.id = c.user_id "
                    "WHERE c.provider = ? AND c.external_id = ?"
                ),
                (provider, external_id),
            )
            row = cur.fetchone()
            return User(id=row[0], email=row[1], created_at=datetime.fromisoformat(row[2])) if row else None
        finally:
            conn.close()

    def create_user(self, email: str) -> User:
        user_id = str(uuid.uuid4())
        now = datetime.now(timezone.utc).isoformat()
        conn = self._connect()
        try:
            cur = conn.cursor()
            cur.execute(
                self._q("INSERT INTO waft_auth_users (id, email, created_at) VALUES (?, ?, ?)"),
                (user_id, email, now),
            )
            conn.commit()
            return User(id=user_id, email=email, created_at=datetime.fromisoformat(now))
        finally:
            conn.close()

    def list_users(self) -> list[User]:
        conn = self._connect()
        try:
            cur = conn.cursor()
            cur.execute("SELECT id, email, created_at FROM waft_auth_users ORDER BY created_at")
            return [
                User(id=row[0], email=row[1], created_at=datetime.fromisoformat(row[2]))
                for row in cur.fetchall()
            ]
        finally:
            conn.close()

    def store_credential(
        self, user_id: str, provider: str, external_id: str, data: str
    ) -> None:
        cred_id = str(uuid.uuid4())
        now = datetime.now(timezone.utc).isoformat()
        conn = self._connect()
        try:
            cur = conn.cursor()
            # Upsert: if provider+external_id already exists, update the data
            cur.execute(
                self._q(
                    "SELECT id FROM waft_auth_credentials WHERE provider = ? AND external_id = ?"
                ),
                (provider, external_id),
            )
            existing = cur.fetchone()
            if existing:
                cur.execute(
                    self._q(
                        "UPDATE waft_auth_credentials SET credential_data = ?, user_id = ? WHERE id = ?"
                    ),
                    (data, user_id, existing[0]),
                )
            else:
                cur.execute(
                    self._q(
                        "INSERT INTO waft_auth_credentials "
                        "(id, user_id, provider, external_id, credential_data, created_at) "
                        "VALUES (?, ?, ?, ?, ?, ?)"
                    ),
                    (cred_id, user_id, provider, external_id, data, now),
                )
            conn.commit()
        finally:
            conn.close()

    def get_credential(self, user_id: str, provider: str) -> tuple[str, str] | None:
        conn = self._connect()
        try:
            cur = conn.cursor()
            cur.execute(
                self._q(
                    "SELECT external_id, credential_data FROM waft_auth_credentials "
                    "WHERE user_id = ? AND provider = ?"
                ),
                (user_id, provider),
            )
            row = cur.fetchone()
            return (row[0], row[1]) if row else None
        finally:
            conn.close()

    def get_credential_by_external(
        self, provider: str, external_id: str
    ) -> tuple[str, str] | None:
        """Returns (user_id, credential_data) for a provider+external_id pair."""
        conn = self._connect()
        try:
            cur = conn.cursor()
            cur.execute(
                self._q(
                    "SELECT user_id, credential_data FROM waft_auth_credentials "
                    "WHERE provider = ? AND external_id = ?"
                ),
                (provider, external_id),
            )
            row = cur.fetchone()
            return (row[0], row[1]) if row else None
        finally:
            conn.close()

    def create_invite(self, email: str) -> str:
        invite_id = str(uuid.uuid4())
        token = uuid.uuid4().hex
        now = datetime.now(timezone.utc).isoformat()
        conn = self._connect()
        try:
            cur = conn.cursor()
            cur.execute(
                self._q(
                    "INSERT INTO waft_auth_invites (id, email, token, created_at) VALUES (?, ?, ?, ?)"
                ),
                (invite_id, email, token, now),
            )
            conn.commit()
            return token
        finally:
            conn.close()

    def has_invite(self, email: str) -> bool:
        conn = self._connect()
        try:
            cur = conn.cursor()
            cur.execute(
                self._q(
                    "SELECT 1 FROM waft_auth_invites WHERE email = ? AND used_at IS NULL"
                ),
                (email,),
            )
            return cur.fetchone() is not None
        finally:
            conn.close()

    def use_invite(self, email: str) -> None:
        now = datetime.now(timezone.utc).isoformat()
        conn = self._connect()
        try:
            cur = conn.cursor()
            cur.execute(
                self._q(
                    "UPDATE waft_auth_invites SET used_at = ? WHERE email = ? AND used_at IS NULL"
                ),
                (now, email),
            )
            conn.commit()
        finally:
            conn.close()
