from __future__ import annotations

from dataclasses import dataclass, field

from fastapi import Depends, Header, HTTPException, Request
from sqlalchemy import select
from sqlalchemy.orm import Session

from ..database import get_db
from ..models import Administrator, Classroom, Computer, DeviceToken, Teacher, utcnow
from ..schemas.schemas import TEACHER_PERMS
from ..security import decode_access_token, sha256


@dataclass
class Principal:
    role: str  # admin | guru
    id: int
    name: str
    perms: dict = field(default_factory=dict)
    classroom_ids: set[int] | None = None  # None = semua

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

    def can(self, perm: str) -> bool:
        return self.is_admin or bool(self.perms.get(perm))

    def require(self, perm: str) -> None:
        if not self.can(perm):
            raise HTTPException(403, f"Anda tidak memiliki izin '{perm}'")

    def can_see(self, comp: Computer) -> bool:
        return self.classroom_ids is None or (comp.classroom_id is not None and comp.classroom_id in self.classroom_ids)


def principal_from_token(db: Session, token: str) -> Principal | None:
    p = decode_access_token(token)
    if not p:
        return None
    if p["role"] == "admin":
        u = db.get(Administrator, p["uid"])
        if not u or not u.is_active:
            return None
        return Principal("admin", u.id, u.full_name, {k: True for k in TEACHER_PERMS}, None)
    u = db.get(Teacher, p["uid"])
    if not u or not u.is_active:
        return None
    cls = {c for (c,) in db.execute(select(Classroom.id).where(Classroom.teacher_id == u.id))}
    perms = {k: bool((u.permissions or {}).get(k, True)) for k in TEACHER_PERMS}
    return Principal("guru", u.id, u.full_name, perms, cls)


def current_user(authorization: str | None = Header(default=None), db: Session = Depends(get_db)) -> Principal:
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(401, "Tidak terautentikasi")
    pr = principal_from_token(db, authorization[7:].strip())
    if not pr:
        raise HTTPException(401, "Token tidak valid atau kedaluwarsa")
    return pr


def admin_only(pr: Principal = Depends(current_user)) -> Principal:
    if not pr.is_admin:
        raise HTTPException(403, "Hanya administrator yang boleh melakukan ini")
    return pr


def client_ip(request: Request) -> str:
    return request.client.host if request.client else ""


def agent_auth(authorization: str | None = Header(default=None), db: Session = Depends(get_db)) -> Computer:
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(401, "Token perangkat dibutuhkan")
    comp = computer_from_device_token(db, authorization[7:].strip())
    if not comp:
        raise HTTPException(401, "Token perangkat tidak valid")
    return comp


def computer_from_device_token(db: Session, token: str) -> Computer | None:
    t = db.execute(select(DeviceToken).where(DeviceToken.token_hash == sha256(token))).scalar_one_or_none()
    if not t or t.revoked or t.expires_at < utcnow():
        return None
    comp = db.get(Computer, t.computer_id)
    if not comp or comp.released:
        return None
    return comp


def visible_computers(db: Session, pr: Principal, ids: list[int] | None = None) -> list[Computer]:
    q = select(Computer).where(Computer.released.is_(False))
    if ids is not None:
        q = q.where(Computer.id.in_(ids))
    if pr.classroom_ids is not None:
        q = q.where(Computer.classroom_id.in_(pr.classroom_ids or {-1}))
    return list(db.execute(q.order_by(Computer.hostname)).scalars())
