from __future__ import annotations

import re
from typing import Literal

from pydantic import BaseModel, Field, field_validator

DOMAIN_RE = re.compile(r"^(?=.{3,190}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$")
EXE_RE = re.compile(r"^[A-Za-z0-9._ \-()+]{1,120}\.(exe|msi|bat|cmd|com)$", re.I)
HHMM = re.compile(r"^([01]\d|2[0-3]):[0-5]\d$")


def norm_domain(v: str) -> str:
    v = v.strip().lower()
    v = re.sub(r"^https?://", "", v).split("/")[0].split(":")[0]
    if v.startswith("www."):
        v = v[4:]
    if not DOMAIN_RE.match(v):
        raise ValueError(f"Domain tidak valid: {v}")
    return v


class SetupIn(BaseModel):
    setup_code: str
    username: str = Field(min_length=3, max_length=64, pattern=r"^[A-Za-z0-9_.\-]+$")
    full_name: str = Field(min_length=2, max_length=120)
    password: str = Field(min_length=8, max_length=128)
    school_name: str | None = None


class LoginIn(BaseModel):
    username: str = Field(max_length=64)
    password: str = Field(max_length=128)


class RefreshIn(BaseModel):
    refresh_token: str


class ChangePasswordIn(BaseModel):
    old_password: str
    new_password: str = Field(min_length=8, max_length=128)


class UserIn(BaseModel):
    username: str = Field(min_length=3, max_length=64, pattern=r"^[A-Za-z0-9_.\-]+$")
    full_name: str = Field(min_length=2, max_length=120)
    email: str | None = Field(default=None, max_length=160)
    nip: str | None = Field(default=None, max_length=40)
    password: str | None = Field(default=None, min_length=8, max_length=128)
    permissions: dict[str, bool] | None = None
    is_active: bool = True


TEACHER_PERMS = ["power", "lock", "message", "screen", "internet", "browser", "downloads", "apps"]

ACTIONS = Literal["shutdown", "restart", "logout", "lock", "unlock", "lock_workstation", "message", "wake", "refresh_policy",
                  "close_browser", "kill_process", "abort_shutdown", "release"]


class CommandIn(BaseModel):
    computer_ids: list[int] = Field(min_length=1, max_length=200)
    action: ACTIONS
    params: dict = Field(default_factory=dict)
    reason: str | None = Field(default=None, max_length=255)
    confirm: bool = False


class ComputerPatch(BaseModel):
    label: str | None = Field(default=None, max_length=120)
    classroom_id: int | None = None
    student_id: int | None = None
    internet_override: Literal["block_all", "allow_all", "none"] | None = None
    exam_lock: bool | None = None
    set_classroom: bool = False
    set_student: bool = False


class ClassIn(BaseModel):
    name: str = Field(min_length=1, max_length=80)
    description: str | None = Field(default=None, max_length=255)
    teacher_id: int | None = None


class StudentIn(BaseModel):
    nis: str | None = Field(default=None, max_length=40)
    full_name: str = Field(min_length=2, max_length=120)
    classroom_id: int | None = None
    computer_id: int | None = None
    is_active: bool = True


class InternetPolicyIn(BaseModel):
    name: str = Field(min_length=2, max_length=100)
    mode: Literal["allow_all", "block_all", "allowlist", "blocklist"] = "blocklist"
    block_social: bool = False
    block_games: bool = False
    block_entertainment: bool = False
    is_active: bool = True
    allowed: list[str] | None = None
    blocked: list[str] | None = None

    @field_validator("allowed", "blocked")
    @classmethod
    def _doms(cls, v):
        return None if v is None else sorted({norm_domain(x) for x in v if x.strip()})


class BrowserPolicyIn(BaseModel):
    name: str = Field(min_length=2, max_length=100)
    max_tabs: int = Field(default=0, ge=0, le=50)
    allowed_browsers: list[Literal["chrome", "edge"]] = ["chrome", "edge"]
    homepage: str | None = Field(default=None, max_length=255)
    block_incognito: bool = True
    downloads_require_approval: bool = True
    exam_mode: bool = False
    is_active: bool = True

    @field_validator("homepage")
    @classmethod
    def _hp(cls, v):
        if v and not re.match(r"^https?://", v):
            raise ValueError("Halaman awal harus diawali http:// atau https://")
        return v or None


class ApplicationPolicyIn(BaseModel):
    name: str = Field(min_length=2, max_length=100)
    mode: Literal["blocklist", "allowlist"] = "blocklist"
    blocked_apps: list[str] = []
    allowed_apps: list[str] = []
    block_store: bool = True
    block_installers: bool = True
    is_active: bool = True

    @field_validator("blocked_apps", "allowed_apps")
    @classmethod
    def _exes(cls, v):
        out = []
        for x in v:
            x = x.strip()
            if x and not EXE_RE.match(x):
                raise ValueError(f"Nama berkas aplikasi tidak valid: {x}")
            if x:
                out.append(x.lower())
        return sorted(set(out))


class AssignmentIn(BaseModel):
    policy_type: Literal["internet", "browser", "application"]
    policy_id: int
    target_type: Literal["all", "classroom", "computer"]
    target_id: int = 0


class ScheduleIn(BaseModel):
    name: str = Field(min_length=2, max_length=100)
    kind: Literal["internet", "browser", "application", "usage"]
    classroom_id: int | None = None
    computer_id: int | None = None
    days: list[int] = [0, 1, 2, 3, 4]
    start_time: str
    end_time: str
    policy_id: int | None = None
    is_active: bool = True

    @field_validator("start_time", "end_time")
    @classmethod
    def _t(cls, v):
        if not HHMM.match(v):
            raise ValueError("Format waktu harus HH:MM")
        return v

    @field_validator("days")
    @classmethod
    def _d(cls, v):
        if not v or any(d < 0 or d > 6 for d in v):
            raise ValueError("Hari 0-6 (0=Senin)")
        return sorted(set(v))


class DecisionIn(BaseModel):
    note: str | None = Field(default=None, max_length=255)
    minutes: int | None = Field(default=None, ge=1, le=240)


class SettingsIn(BaseModel):
    values: dict[str, str]


# ---- Agent --------------------------------------------------------------------------
class AgentRegisterIn(BaseModel):
    enrollment_key: str
    device_uid: str = Field(min_length=8, max_length=64, pattern=r"^[A-Za-z0-9\-]+$")
    hostname: str = Field(max_length=120)
    ip_address: str | None = Field(default=None, max_length=45)
    mac_address: str | None = Field(default=None, max_length=17)
    os_name: str | None = Field(default=None, max_length=160)
    windows_user: str | None = Field(default=None, max_length=120)
    agent_version: str | None = Field(default=None, max_length=20)


class AgentDownloadIn(BaseModel):
    file_name: str = Field(max_length=255)
    file_size: int | None = Field(default=None, ge=0)
    file_type: str | None = Field(default=None, max_length=120)
    source_url: str = Field(max_length=2000)
    browser: str | None = Field(default=None, max_length=20)


class AgentAppRequestIn(BaseModel):
    app_name: str = Field(max_length=255)
    request_type: Literal["install", "run", "store"] = "install"
    detail: str | None = Field(default=None, max_length=500)


class AgentEventIn(BaseModel):
    type: Literal["blocked_site", "blocked_app", "tab_limit", "policy_error", "tamper", "info"]
    detail: dict = Field(default_factory=dict)
