from __future__ import annotations

import sqlite3
from datetime import datetime

from backend.features.accounts.models import (
    MembershipAccount,
    MembershipRecord,
    SessionRecord,
    UserRecord,
)


def _datetime(value: str) -> datetime:
    return datetime.fromisoformat(value)


def _optional_datetime(value: str | None) -> datetime | None:
    return _datetime(value) if value else None


def _user(row: sqlite3.Row) -> UserRecord:
    return UserRecord(
        id=int(row["id"]),
        username=str(row["username"]),
        username_key=str(row["username_key"]),
        password_hash=str(row["password_hash"]),
        is_admin=bool(row["is_admin"]),
        status=str(row["status"]),
        created_at=_datetime(str(row["created_at"])),
        updated_at=_datetime(str(row["updated_at"])),
    )


def _membership(row: sqlite3.Row, prefix: str = "") -> MembershipRecord:
    return MembershipRecord(
        user_id=int(row[f"{prefix}user_id"]),
        state=str(row[f"{prefix}state"]),
        expires_at=_optional_datetime(row[f"{prefix}expires_at"]),
        is_permanent=bool(row[f"{prefix}is_permanent"]),
        daily_llm_limit=int(row[f"{prefix}daily_llm_limit"]),
        updated_at=_datetime(str(row[f"{prefix}updated_at"])),
        updated_by=(
            int(row[f"{prefix}updated_by"]) if row[f"{prefix}updated_by"] is not None else None
        ),
    )


class AccountRepository:
    @staticmethod
    def count_users(connection: sqlite3.Connection) -> int:
        return int(connection.execute("SELECT COUNT(*) FROM users").fetchone()[0])

    @staticmethod
    def create_user(
        connection: sqlite3.Connection,
        username: str,
        username_key: str,
        password_hash: str,
        is_admin: bool,
        now: datetime,
    ) -> UserRecord:
        timestamp = now.isoformat(timespec="seconds")
        cursor = connection.execute(
            """
            INSERT INTO users
                (username, username_key, password_hash, is_admin, created_at, updated_at)
            VALUES (?, ?, ?, ?, ?, ?)
            """,
            (username, username_key, password_hash, int(is_admin), timestamp, timestamp),
        )
        return UserRecord(
            id=int(cursor.lastrowid),
            username=username,
            username_key=username_key,
            password_hash=password_hash,
            is_admin=is_admin,
            status="active",
            created_at=now,
            updated_at=now,
        )

    @staticmethod
    def get_user_by_username_key(
        connection: sqlite3.Connection, username_key: str
    ) -> UserRecord | None:
        row = connection.execute(
            "SELECT * FROM users WHERE username_key = ?", (username_key,)
        ).fetchone()
        return _user(row) if row else None

    @staticmethod
    def get_user_by_id(connection: sqlite3.Connection, user_id: int) -> UserRecord | None:
        row = connection.execute("SELECT * FROM users WHERE id = ?", (user_id,)).fetchone()
        return _user(row) if row else None

    @staticmethod
    def create_default_membership(
        connection: sqlite3.Connection, user_id: int, now: datetime
    ) -> MembershipRecord:
        timestamp = now.isoformat(timespec="seconds")
        connection.execute(
            """
            INSERT INTO memberships (user_id, state, daily_llm_limit, updated_at)
            VALUES (?, 'inactive', 50, ?)
            """,
            (user_id, timestamp),
        )
        return MembershipRecord(user_id, "inactive", None, False, 50, now, None)

    @staticmethod
    def get_membership(connection: sqlite3.Connection, user_id: int) -> MembershipRecord | None:
        row = connection.execute(
            "SELECT * FROM memberships WHERE user_id = ?", (user_id,)
        ).fetchone()
        return _membership(row) if row else None

    @staticmethod
    def update_password(
        connection: sqlite3.Connection, user_id: int, password_hash: str, now: datetime
    ) -> None:
        connection.execute(
            "UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?",
            (password_hash, now.isoformat(timespec="seconds"), user_id),
        )

    @staticmethod
    def create_session(
        connection: sqlite3.Connection,
        token_hash: str,
        csrf_hash: str,
        user_id: int,
        now: datetime,
        expires_at: datetime,
    ) -> None:
        timestamp = now.isoformat(timespec="seconds")
        connection.execute(
            """
            INSERT INTO sessions
                (token_hash, user_id, csrf_hash, created_at, expires_at, last_seen_at)
            VALUES (?, ?, ?, ?, ?, ?)
            """,
            (
                token_hash,
                user_id,
                csrf_hash,
                timestamp,
                expires_at.isoformat(timespec="seconds"),
                timestamp,
            ),
        )

    @staticmethod
    def get_session(connection: sqlite3.Connection, token_hash: str) -> SessionRecord | None:
        row = connection.execute(
            """
            SELECT
                u.*,
                m.user_id AS membership_user_id,
                m.state AS membership_state,
                m.expires_at AS membership_expires_at,
                m.is_permanent AS membership_is_permanent,
                m.daily_llm_limit AS membership_daily_llm_limit,
                m.updated_at AS membership_updated_at,
                m.updated_by AS membership_updated_by,
                s.token_hash AS session_token_hash,
                s.csrf_hash AS session_csrf_hash,
                s.expires_at AS session_expires_at
            FROM sessions s
            JOIN users u ON u.id = s.user_id
            JOIN memberships m ON m.user_id = u.id
            WHERE s.token_hash = ?
            """,
            (token_hash,),
        ).fetchone()
        if row is None:
            return None
        return SessionRecord(
            token_hash=str(row["session_token_hash"]),
            csrf_hash=str(row["session_csrf_hash"]),
            user=_user(row),
            membership=_membership(row, "membership_"),
            expires_at=_datetime(str(row["session_expires_at"])),
        )

    @staticmethod
    def delete_session(connection: sqlite3.Connection, token_hash: str) -> None:
        connection.execute("DELETE FROM sessions WHERE token_hash = ?", (token_hash,))

    @staticmethod
    def delete_other_sessions(
        connection: sqlite3.Connection, user_id: int, token_hash: str
    ) -> None:
        connection.execute(
            "DELETE FROM sessions WHERE user_id = ? AND token_hash <> ?",
            (user_id, token_hash),
        )

    @staticmethod
    def get_encrypted_profile(connection: sqlite3.Connection, user_id: int) -> sqlite3.Row | None:
        return connection.execute(
            "SELECT encrypted_payload, updated_at FROM birth_profiles WHERE user_id = ?",
            (user_id,),
        ).fetchone()

    @staticmethod
    def save_encrypted_profile(
        connection: sqlite3.Connection,
        user_id: int,
        encrypted_payload: str,
        now: datetime,
    ) -> None:
        timestamp = now.isoformat(timespec="seconds")
        connection.execute(
            """
            INSERT INTO birth_profiles (user_id, encrypted_payload, created_at, updated_at)
            VALUES (?, ?, ?, ?)
            ON CONFLICT(user_id) DO UPDATE SET
                encrypted_payload = excluded.encrypted_payload,
                updated_at = excluded.updated_at
            """,
            (user_id, encrypted_payload, timestamp, timestamp),
        )

    @staticmethod
    def delete_profile(connection: sqlite3.Connection, user_id: int) -> None:
        connection.execute("DELETE FROM birth_profiles WHERE user_id = ?", (user_id,))

    @staticmethod
    def list_memberships(connection: sqlite3.Connection) -> tuple[MembershipAccount, ...]:
        rows = connection.execute(
            """
            SELECT
                u.*,
                m.user_id AS membership_user_id,
                m.state AS membership_state,
                m.expires_at AS membership_expires_at,
                m.is_permanent AS membership_is_permanent,
                m.daily_llm_limit AS membership_daily_llm_limit,
                m.updated_at AS membership_updated_at,
                m.updated_by AS membership_updated_by
            FROM users u
            JOIN memberships m ON m.user_id = u.id
            ORDER BY u.id
            """
        ).fetchall()
        return tuple(MembershipAccount(_user(row), _membership(row, "membership_")) for row in rows)

    @staticmethod
    def get_membership_account(
        connection: sqlite3.Connection, user_id: int
    ) -> MembershipAccount | None:
        row = connection.execute(
            """
            SELECT
                u.*,
                m.user_id AS membership_user_id,
                m.state AS membership_state,
                m.expires_at AS membership_expires_at,
                m.is_permanent AS membership_is_permanent,
                m.daily_llm_limit AS membership_daily_llm_limit,
                m.updated_at AS membership_updated_at,
                m.updated_by AS membership_updated_by
            FROM users u
            JOIN memberships m ON m.user_id = u.id
            WHERE u.id = ?
            """,
            (user_id,),
        ).fetchone()
        return MembershipAccount(_user(row), _membership(row, "membership_")) if row else None

    @staticmethod
    def update_membership(
        connection: sqlite3.Connection,
        user_id: int,
        state: str,
        expires_at: datetime | None,
        is_permanent: bool,
        daily_limit: int,
        updated_by: int,
        now: datetime,
    ) -> MembershipRecord | None:
        connection.execute(
            """
            UPDATE memberships SET
                state = ?, expires_at = ?, is_permanent = ?, daily_llm_limit = ?,
                updated_at = ?, updated_by = ?
            WHERE user_id = ?
            """,
            (
                state,
                expires_at.isoformat(timespec="seconds") if expires_at else None,
                int(is_permanent),
                daily_limit,
                now.isoformat(timespec="seconds"),
                updated_by,
                user_id,
            ),
        )
        return AccountRepository.get_membership(connection, user_id)


class SystemCredentialRepository:
    @staticmethod
    def list_status(connection: sqlite3.Connection) -> dict[str, str]:
        return {
            str(row["name"]): str(row["updated_at"])
            for row in connection.execute("SELECT name, updated_at FROM system_credentials")
        }

    @staticmethod
    def save(
        connection: sqlite3.Connection,
        name: str,
        encrypted_value: str,
        updated_by: int,
        now: datetime,
    ) -> None:
        connection.execute(
            """
            INSERT INTO system_credentials (name, encrypted_value, updated_at, updated_by)
            VALUES (?, ?, ?, ?)
            ON CONFLICT(name) DO UPDATE SET
                encrypted_value = excluded.encrypted_value,
                updated_at = excluded.updated_at,
                updated_by = excluded.updated_by
            """,
            (name, encrypted_value, now.isoformat(timespec="seconds"), updated_by),
        )

    @staticmethod
    def get_encrypted(connection: sqlite3.Connection, name: str) -> str | None:
        row = connection.execute(
            "SELECT encrypted_value FROM system_credentials WHERE name = ?", (name,)
        ).fetchone()
        return str(row["encrypted_value"]) if row else None
