"""Authentication: session cookies, password hashing, magic-link tokens."""

import os
import secrets
import time
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from typing import Optional

import bcrypt
from itsdangerous import BadSignature, SignatureExpired, URLSafeTimedSerializer

from db import execute, query_one

SECRET_KEY = os.environ.get("SECRET_KEY", "")
if not SECRET_KEY:
    raise RuntimeError("SECRET_KEY env var is required")

SESSION_COOKIE = "ts_session"
SESSION_TTL_DAYS = 30
MAGIC_LINK_TTL_HOURS = 48

_signer = URLSafeTimedSerializer(SECRET_KEY, salt="ts-session-v1")


@dataclass
class CurrentUser:
    id: int
    email: str
    name: Optional[str]
    role: str  # 'admin' | 'internal' | 'client'

    @property
    def is_internal(self) -> bool:
        return self.role in ("admin", "internal")

    @property
    def is_admin(self) -> bool:
        return self.role == "admin"


def hash_password(plain: str) -> str:
    return bcrypt.hashpw(plain.encode(), bcrypt.gensalt()).decode()


def verify_password(plain: str, hashed: str) -> bool:
    if not hashed:
        return False
    try:
        return bcrypt.checkpw(plain.encode(), hashed.encode())
    except ValueError:
        return False


def create_session_cookie(user_id: int) -> str:
    return _signer.dumps({"uid": user_id, "iat": int(time.time())})


def read_session_cookie(token: Optional[str]) -> Optional[int]:
    if not token:
        return None
    try:
        data = _signer.loads(token, max_age=SESSION_TTL_DAYS * 86400)
        return int(data["uid"])
    except (BadSignature, SignatureExpired, KeyError, ValueError, TypeError):
        return None


def load_user(user_id: int) -> Optional[CurrentUser]:
    row = query_one(
        "SELECT id, email, name, role FROM user WHERE id = ?", (user_id,)
    )
    if not row:
        return None
    return CurrentUser(id=row["id"], email=row["email"], name=row["name"], role=row["role"])


def issue_magic_link(user_id: int, client_id_scope: Optional[int] = None) -> str:
    token = secrets.token_urlsafe(32)
    expires_at = datetime.now(timezone.utc) + timedelta(hours=MAGIC_LINK_TTL_HOURS)
    execute(
        "INSERT INTO magic_link (token, user_id, client_id_scope, expires_at) VALUES (?, ?, ?, ?)",
        (token, user_id, client_id_scope, expires_at),
    )
    return token


def consume_magic_link(token: str) -> Optional[CurrentUser]:
    row = query_one(
        """SELECT m.user_id, m.expires_at, m.used_at, u.id, u.email, u.name, u.role
           FROM magic_link m JOIN user u ON u.id = m.user_id
           WHERE m.token = ?""",
        (token,),
    )
    if not row:
        return None
    if row["used_at"] is not None:
        return None
    expires_at = row["expires_at"]
    if isinstance(expires_at, str):
        expires_at = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
    if expires_at.tzinfo is None:
        expires_at = expires_at.replace(tzinfo=timezone.utc)
    if expires_at < datetime.now(timezone.utc):
        return None
    execute(
        "UPDATE magic_link SET used_at = CURRENT_TIMESTAMP WHERE token = ?", (token,)
    )
    execute("UPDATE user SET last_login_at = CURRENT_TIMESTAMP WHERE id = ?", (row["user_id"],))
    return CurrentUser(id=row["id"], email=row["email"], name=row["name"], role=row["role"])
