"""Shared field types.

`.local` / `.internal` hostnames are normal for a self-hosted ISP tool, but
email-validator treats them as reserved special-use names and rejects them during
*syntax* validation (check_deliverability=False does not help, and test_environment=True
only whitelists the literal `test` domain). We drop the internal names we actually use
from its reserved list, then validate syntax normally.
"""
from datetime import datetime, timezone
from typing import Annotated

import email_validator
from pydantic import AfterValidator, BeforeValidator, EmailStr
from pydantic_core import PydanticCustomError

from app.core.time import DHAKA

#: Internal-only suffixes we permit in user emails.
_ALLOWED_INTERNAL_DOMAINS = {"local", "internal", "lan", "home", "corp"}

email_validator.SPECIAL_USE_DOMAIN_NAMES = [
    name
    for name in email_validator.SPECIAL_USE_DOMAIN_NAMES
    if name not in _ALLOWED_INTERNAL_DOMAINS
]


def _validate_internal_email(value: str) -> str:
    if not isinstance(value, str):
        raise PydanticCustomError("value_error", "email must be a string")
    try:
        result = email_validator.validate_email(value, check_deliverability=False)
    except email_validator.EmailNotValidError as exc:
        raise PydanticCustomError(
            "value_error", "not a valid email address: {reason}", {"reason": str(exc)}
        ) from exc
    return result.normalized.lower()


#: Email that accepts internal domains (admin@isp.local) but still rejects malformed input.
InternalEmail = Annotated[str, BeforeValidator(_validate_internal_email)]

#: Trim surrounding whitespace on free-text fields.
TrimmedStr = Annotated[str, AfterValidator(lambda v: v.strip())]


def normalize_bd_phone(value: str | None) -> str | None:
    """Bangladesh mobile numbers: 11 digits starting 01, optionally +880-prefixed."""
    if value is None or not value.strip():
        return None
    digits = "".join(c for c in value if c.isdigit())
    if digits.startswith("880"):
        digits = "0" + digits[3:]
    if len(digits) != 11 or not digits.startswith("01"):
        raise ValueError("phone must be a valid 11-digit BD mobile number (01XXXXXXXXX)")
    return digits


#: Optional BD mobile number, stored normalized as 01XXXXXXXXX.
BDPhone = Annotated[str | None, AfterValidator(normalize_bd_phone)]


def _to_dhaka(value: datetime | None) -> datetime | None:
    """Render a stored UTC timestamp in Asia/Dhaka for the API response."""
    if value is None:
        return None
    if value.tzinfo is None:
        value = value.replace(tzinfo=timezone.utc)
    return value.astimezone(DHAKA)


#: Timestamps are stored as UTC but always serialized with the +06:00 Dhaka offset,
#: so clients never have to guess the intended timezone.
DhakaDateTime = Annotated[datetime, AfterValidator(_to_dhaka)]

__all__ = ["InternalEmail", "TrimmedStr", "DhakaDateTime", "BDPhone", "normalize_bd_phone", "EmailStr"]
