"""Öffentliches QR-Stempeln (statischer firmenweiter QR-Code).

Kein Bearer-Token, KEINE Ed25519-Geräte-Signatur (anders als der Kiosk):
ein privates Handy hat keinen Geräteschlüssel. Stattdessen Identifikation per
Personalnummer + PIN. Härtung: Opt-in (default OFF), PIN-Lockout, IP-Rate-Limit,
gehashtes rotierbares Token, 120s-Session, AuditLog.

⚠ SICHERHEIT: Dieser Pfad ist STRIKT SCHWÄCHER als der Kiosk-Pfad – keine
Geräte-Signatur, kein Replay-Nonce, keine IP-Whitelist. Er tauscht Sicherheit
gegen BYOD-Komfort. Pro Firma nur aktivieren wenn wirklich benötigt.
"""
from __future__ import annotations

import hashlib

from fastapi import APIRouter, Depends, HTTPException, Query, Request
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.database import get_db
from app.core.dependencies import get_client_ip
from app.core.limiter import limiter
from app.models.audit_log import AuditLog
from app.models.company import Company
from app.models.time_entry import EntrySource
from app.models.user import User
from app.schemas.public_stamp import (
    PublicStampActionRequest,
    PublicStampActionResponse,
    PublicStampAuthRequest,
    PublicStampAuthResponse,
    PublicStampCompanyInfo,
)
from app.schemas.time_entry import StampInRequest, TimeEntryOut
from app.services.kiosk_auth_service import kiosk_auth_service, _display_name
from app.services.public_stamp_session_service import (
    PUBLIC_STAMP_SESSION_TTL,
    public_stamp_session_service,
)
from app.services.time_service import time_service

router = APIRouter(prefix="/time/public", tags=["Public Stamping"])


def _hash_token(token: str) -> str:
    return hashlib.sha256(token.encode("utf-8")).hexdigest()


async def _company_from_token(token: str, db: AsyncSession, *, require_enabled: bool) -> Company:
    company = await db.scalar(
        select(Company).where(Company.public_stamp_token_hash == _hash_token(token))
    )
    if company is None:
        raise HTTPException(status_code=404, detail="QR-Code ungültig.")
    if require_enabled and not company.public_stamp_enabled:
        raise HTTPException(status_code=403, detail="QR-Stempeln ist für dieses Unternehmen deaktiviert.")
    return company


async def _status(user: User, db: AsyncSession) -> dict:
    """Aktuellen Stempel-Status + heutige Einträge ermitteln."""
    open_entry = await time_service._get_open_entry(user.id, db)
    today = await time_service.get_today(user, db)
    return {
        "open": open_entry is not None,
        "on_break": open_entry is not None and open_entry.break_start is not None,
        "today": [TimeEntryOut.model_validate(e) for e in today],
    }


@router.get("/company", response_model=PublicStampCompanyInfo)
@limiter.limit("30/minute")
async def public_company_info(
    request: Request,
    t: str = Query(..., min_length=8),
    db: AsyncSession = Depends(get_db),
):
    """Firmen-Header für die Stempel-Seite. Gibt auch bei deaktiviertem
    Feature den Namen zurück, damit die Seite einen Hinweis zeigen kann."""
    company = await _company_from_token(t, db, require_enabled=False)
    return PublicStampCompanyInfo(company_name=company.name, enabled=company.public_stamp_enabled)


@router.post("/auth", response_model=PublicStampAuthResponse)
@limiter.limit("10/minute")
async def public_auth(
    request: Request,
    body: PublicStampAuthRequest,
    db: AsyncSession = Depends(get_db),
):
    """Personalnummer + PIN → Kurz-Session + aktueller Status."""
    company = await _company_from_token(body.token, db, require_enabled=True)
    user = await kiosk_auth_service.login_pin_public(
        body.personnel_number, body.pin, company.id, db
    )

    session_token = await public_stamp_session_service.create_session(user.id, company.id)

    db.add(AuditLog(
        company_id=company.id,
        user_id=user.id,
        action="public_stamp_login",
        entity_type="user",
        entity_id=user.id,
        ip=get_client_ip(request),
    ))

    # Status vor dem Commit ermitteln (siehe public_action: commit() setzt
    # SET LOCAL app.bypass_rls zurück → RLS würde danach alle Zeilen filtern).
    status = await _status(user, db)
    await db.commit()

    return PublicStampAuthResponse(
        session_token=session_token,
        user_name=_display_name(user),
        expires_in_seconds=PUBLIC_STAMP_SESSION_TTL,
        **status,
    )


@router.post("/action", response_model=PublicStampActionResponse)
@limiter.limit("30/minute")
async def public_action(
    request: Request,
    body: PublicStampActionRequest,
    db: AsyncSession = Depends(get_db),
):
    """Stempel-Aktion über eine gültige Kurz-Session ausführen."""
    session = await public_stamp_session_service.require_session(body.session_token)

    user = await db.get(User, session["user_id"])
    if user is None or not user.is_active or str(user.company_id) != session["company_id"]:
        raise HTTPException(status_code=401, detail="Session ungültig.")

    warnings: list[str] = []
    if body.action == "in":
        _, warnings = await time_service.stamp_in(
            user, StampInRequest(source=EntrySource.KIOSK, note=body.note), db
        )
    elif body.action == "out":
        _, warnings = await time_service.stamp_out(user, body.note, db)
    elif body.action == "break_start":
        await time_service.break_start(user, db)
    elif body.action == "break_end":
        await time_service.break_end(user, db)

    db.add(AuditLog(
        company_id=user.company_id,
        user_id=user.id,
        action=f"public_stamp_{body.action}",
        entity_type="user",
        entity_id=user.id,
        ip=get_client_ip(request),
    ))

    # Status VOR dem Commit ermitteln: ein zwischenzeitliches commit() beendet die
    # Transaktion und setzt `SET LOCAL app.bypass_rls` zurück – danach würde RLS
    # (mangels app.company_id auf dieser öffentlichen Route) alle Zeilen filtern.
    status = await _status(user, db)
    await db.commit()

    return PublicStampActionResponse(warnings=warnings, **status)
