"""
services/auth_service.py — Authentication & Access Key management
==================================================================
Backed by PostgreSQL (`participant` table). All DB I/O goes through
`services.participant_repo`.

JWT payload fields:
    sub   → candidate_id   (participant.id / participant_id)
    name  → candidate name
    stage → current_stage
"""
from __future__ import annotations

from datetime import datetime, timedelta, timezone

from jose import JWTError, jwt

from config import settings
from services import participant_repo
from utils.logger import get_logger

log = get_logger(__name__)


# ── JWT ──────────────────────────────────────────────────────────────────────

def create_token(candidate_id: str, name: str, stage: int) -> str:
    expire = datetime.now(timezone.utc) + timedelta(minutes=settings.JWT_EXPIRE_MINUTES)
    payload = {
        "sub":   str(candidate_id),
        "name":  name,
        "stage": int(stage),
        "exp":   int(expire.timestamp()),
    }
    return jwt.encode(payload, settings.JWT_SECRET_KEY, algorithm=settings.JWT_ALGORITHM)


def decode_token(token: str) -> dict | None:
    try:
        return jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[settings.JWT_ALGORITHM])
    except JWTError as exc:
        log.debug("JWT decode failed: %s", exc)
        return None


# ── Candidate lookup (DB-backed) ─────────────────────────────────────────────

def validate_access_key(raw_key: str) -> dict | None:
    """
    Validate a candidate's access key against the participant table.
    Returns the normalized candidate dict, or None.
    """
    if not raw_key or not raw_key.strip():
        log.info("login attempt with empty access_key — rejected")
        return None

    log.info("login attempt → access_key=%s", raw_key.strip().upper())

    try:
        candidate = participant_repo.find_by_access_key(raw_key)
    except Exception as exc:
        # Bubble up to the route layer so it can return a 503.
        log.exception("DB error during access_key validation: %s", exc)
        raise

    if not candidate:
        return None

    # Best-effort last_login update; never blocks login.
    try:
        participant_repo.touch_last_login(candidate["id"])
    except Exception as exc:  # noqa: BLE001
        log.warning("touch_last_login failed (non-fatal): %s", exc)

    return candidate


def get_candidate(candidate_id: str) -> dict | None:
    return participant_repo.find_by_id(candidate_id)


def advance_stage(candidate_id: str, new_stage: int) -> dict | None:
    """
    Move a candidate to the next interview stage.
    Currently a best-effort UPDATE on the participant table; if the
    target column doesn't exist in the schema, we return the cached
    candidate dict with `current_stage` overridden so the rest of the
    flow keeps working.
    """
    from psycopg2 import sql as _sql
    from utils import database as _db

    table = _sql.Identifier(settings.DB_TABLE_PARTICIPANT)
    for stage_col in ("current_stage", "stage", "stage_no", "interview_stage"):
        for id_col in ("id", "participant_id", "candidate_id"):
            query = _sql.SQL(
                "UPDATE {table} SET {stage} = %s WHERE {idc}::text = %s"
            ).format(
                table=table,
                stage=_sql.Identifier(stage_col),
                idc=_sql.Identifier(id_col),
            )
            try:
                with _db.get_cursor(dict_rows=False) as cur:
                    cur.execute(query, (int(new_stage), str(candidate_id)))
                    if cur.rowcount > 0:
                        log.info("advance_stage → id=%s new_stage=%d", candidate_id, new_stage)
                        return participant_repo.find_by_id(candidate_id)
            except Exception as exc:
                log.debug("advance_stage attempt %s/%s failed: %s", stage_col, id_col, exc)
                continue

    log.warning("advance_stage could not persist new_stage=%d for id=%s", new_stage, candidate_id)
    cand = participant_repo.find_by_id(candidate_id) or {}
    if cand:
        cand["current_stage"] = int(new_stage)
    return cand or None


# ── Stage helpers ────────────────────────────────────────────────────────────

def get_redirect_url(stage: int) -> str:
    return settings.STAGE_ROUTES.get(int(stage), "/people_hub_assessment")


def get_stage_name(stage: int) -> str:
    return settings.STAGE_NAMES.get(int(stage), "Assessment")
