"""PostgreSQL-backed user store for authentication.

Manages a `users` table with hashed passwords and roles.
Provides lookup by username for the login flow.
Table DDL is auto-applied on first connection.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Optional

import psycopg2
import psycopg2.extras
from loguru import logger
from passlib.context import CryptContext

from app.config.settings import settings


# bcrypt context — work factor 12 is a good production default.
_PWD_CTX = CryptContext(schemes=["bcrypt"], deprecated="auto")

# DDL executed once to ensure the table exists.
_CREATE_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS users (
    id          UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    username    VARCHAR(100) UNIQUE NOT NULL,
    hashed_pw   TEXT         NOT NULL,
    role        VARCHAR(50)  NOT NULL DEFAULT 'readonly',
    is_active   BOOLEAN      NOT NULL DEFAULT TRUE,
    created_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);
"""


@dataclass
class UserRecord:
    """A single row from the users table."""

    id: str
    username: str
    hashed_pw: str
    role: str
    is_active: bool


class PostgresUserStore:
    """Read and verify users stored in the PostgreSQL users table.

    The connection is opened on first use and shared for the lifetime
    of the singleton instance wired by bootstrap.
    """

    def __init__(self) -> None:
        """Initialise the store and ensure the users table exists."""
        self._conn = psycopg2.connect(
            host=settings.postgres_host,
            port=settings.postgres_port,
            user=settings.postgres_user,
            password=settings.postgres_password,
            dbname=settings.postgres_db,
            cursor_factory=psycopg2.extras.RealDictCursor,
        )
        self._conn.autocommit = True
        self._ensure_table()

    def _ensure_table(self) -> None:
        """Create the users table if it does not already exist."""
        with self._conn.cursor() as cur:
            # Enable pgcrypto so gen_random_uuid() is available for UUID primary keys.
            try:
                cur.execute("CREATE EXTENSION IF NOT EXISTS pgcrypto;")
            except Exception:
                self._conn.rollback()
            cur.execute(_CREATE_TABLE_SQL)

    def get_by_username(self, username: str) -> Optional[UserRecord]:
        """Return a UserRecord for the given username, or None if not found."""
        with self._conn.cursor() as cur:
            cur.execute(
                "SELECT id, username, hashed_pw, role, is_active "
                "FROM users WHERE username = %s",
                (username,),
            )
            row = cur.fetchone()
        if row is None:
            return None
        return UserRecord(
            id=str(row["id"]),
            username=row["username"],
            hashed_pw=row["hashed_pw"],
            role=row["role"],
            is_active=row["is_active"],
        )

    def verify_password(self, plain: str, hashed: str) -> bool:
        """Return True if `plain` matches the stored bcrypt hash."""
        return _PWD_CTX.verify(plain, hashed)

    def authenticate(self, username: str, password: str) -> Optional[UserRecord]:
        """Return the UserRecord if credentials are valid, else None."""
        user = self.get_by_username(username)
        if user is None or not user.is_active:
            return None
        if not self.verify_password(password, user.hashed_pw):
            return None
        return user

    @staticmethod
    def hash_password(plain: str) -> str:
        """Hash a plain-text password with bcrypt."""
        return _PWD_CTX.hash(plain)
