"""Pure value objects + policy for the Identity context.

Behavior mirrors ``src/auth_api.py`` exactly (same regex, same Vietnamese messages,
same password rules) so the layered slice and the live monolith stay in lock-step.
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from enum import Enum

from src.domain.identity.errors import InvalidInstitutionalEmail, WeakPassword
from src.shared_kernel.value_object import ValueObject

# Authoritative *domain* allow-list for UMP/UMC faculty email
# (mirrors auth_api.INSTITUTIONAL_EMAIL_RE).
_INSTITUTIONAL_EMAIL_RE = re.compile(
    r"^[a-zA-Z0-9._%+-]+@(ump|umc)\.edu\.vn\Z", re.IGNORECASE
)

_INVALID_EMAIL_MSG = (
    "Email phải là địa chỉ UMP hoặc UMC hợp lệ "
    "(dạng ten@ump.edu.vn hoặc ten@umc.edu.vn)."
)

MAX_PASSWORD_INPUT_CHARS = 512


class Role(str, Enum):
    """The three canonical system roles (FE labels map to these 1:1)."""

    admin = "admin"      # Quản trị viên
    editor = "editor"    # Hội đồng (council)
    viewer = "viewer"    # Người nộp đơn (applicant)


@dataclass(frozen=True)
class InstitutionalEmail(ValueObject):
    """A normalized, validated UMP/UMC institutional email."""

    value: str

    @classmethod
    def parse(cls, raw: str) -> "InstitutionalEmail":
        normalized = (raw or "").strip().lower()
        if not _INSTITUTIONAL_EMAIL_RE.match(normalized):
            raise InvalidInstitutionalEmail(_INVALID_EMAIL_MSG)
        return cls(normalized)

    def __str__(self) -> str:  # pragma: no cover - trivial
        return self.value


def assert_password_policy(password: str) -> None:
    """Raise :class:`WeakPassword` if ``password`` violates the policy.

    Exact mirror of ``auth_api._assert_password_policy`` — messages preserved so
    API responses are identical pre/post cut-over.
    """
    if len(password) < 6:
        raise WeakPassword("Mật khẩu tối thiểu 6 ký tự.")
    if len(password) > MAX_PASSWORD_INPUT_CHARS:
        raise WeakPassword("Mật khẩu quá dài.")
    if not re.search(r"[a-z]", password):
        raise WeakPassword("Mật khẩu phải có ít nhất một chữ cái thường.")
    if not re.search(r"[A-Z]", password):
        raise WeakPassword("Mật khẩu phải có ít nhất một chữ cái hoa.")
    if not re.search(r"\d", password):
        raise WeakPassword("Mật khẩu phải có ít nhất một chữ số.")
    if not re.search(r"[^A-Za-z0-9]", password):
        raise WeakPassword(
            "Mật khẩu phải có ít nhất một ký tự đặc biệt (không chỉ chữ và số)."
        )
