"""TypeID implementation with type + domain + suffix.."""

from typing import cast
from uuid import UUID

from uuid_utils import uuid7

from . import base32
from .const import DOMAIN_REGEX, MAX_DOMAIN_LENGTH, MAX_TYPE_LENGTH, SUFFIX_LENGTH
from .exceptions import (
    DomainValidationException,
    InvalidTypeIDStringException,
    SuffixValidationException,
    TypeValidationException,
)


class TypeID:
    """TypeID with type, domain and suffix."""

    type: str
    domain: str
    suffix: str

    def __init__(self, type: str, domain: str, suffix: str | None = None) -> None:
        _validate_type(type)
        self.type = type
        _validate_domain(domain)
        self.domain = domain

        if not suffix:
            self.suffix = _convert_uuid_to_b32(cast("UUID", uuid7()))
        else:
            _validate_suffix(suffix)
            self.suffix = suffix

    @classmethod
    def from_string(cls, string: str):
        """Create a TypeID object from a string."""
        try:
            type_, domain, suffix = string.split("_")
        except ValueError as err:
            raise InvalidTypeIDStringException from err

        return cls(type=type_, domain=domain, suffix=suffix)

    @classmethod
    def from_uuid(cls, suffix: UUID, type: str, domain: str):
        """Create a TypeID from a UUID."""
        suffix_str = _convert_uuid_to_b32(suffix)
        return TypeID(type=type, domain=domain, suffix=suffix_str)

    @property
    def uuid(self) -> UUID:
        """UUID embedded in the TypeID."""
        return _convert_b32_to_uuid(self.suffix)

    def __str__(self) -> str:
        """TypeID as a string."""
        return f"{self.type}_{self.domain}_{self.suffix}"

    def __repr__(self):
        """TypeID as a representation string."""
        return f"{self.__class__.__name__}({str(self)!r})"

    def __eq__(self, value: object) -> bool:
        """Test TypeID for equality."""
        if not isinstance(value, TypeID):
            return False
        return value.type == self.type and value.domain == self.domain and value.suffix == self.suffix

    def __hash__(self) -> int:
        """TypeID hash."""
        return hash((self.type, self.domain, self.suffix))


def _convert_uuid_to_b32(uuid_instance: UUID) -> str:
    return base32.encode(uuid_instance.bytes)


def _convert_b32_to_uuid(b32: str) -> UUID:
    return UUID(bytes=bytes(base32.decode(b32)))


def _validate_type(type: str) -> None:
    if len(type) > MAX_TYPE_LENGTH or not type.isalpha() or not type.islower():
        raise TypeValidationException(f"Invalid type: '{type}'")


def _validate_domain(domain: str) -> None:
    if len(domain) > MAX_DOMAIN_LENGTH or not DOMAIN_REGEX.match(domain):
        raise DomainValidationException(f"Invalid domain: '{domain}'")


def _validate_suffix(suffix: str) -> None:
    if len(suffix) != SUFFIX_LENGTH or any(symbol not in base32.ALPHABET for symbol in suffix) or suffix[0] > "7":
        raise SuffixValidationException(f"Invalid suffix: '{suffix}'")
    try:
        base32.decode(suffix)
    except Exception as exc:
        raise SuffixValidationException(f"Invalid suffix: {suffix}.") from exc


TYPEID_PARTS = 3
TYPEID_SUFFIX_LENGTH = 26


def is_type_id(value: str, required_type: str | None = None):
    """Check whether the given string is a typeID.

    Optionally checks if the TypeID is of the required_type.
    """
    if value.count("_") != TYPEID_PARTS - 1:
        return False

    type_, domain, suffix = value.split("_")
    return len(suffix) == TYPEID_SUFFIX_LENGTH and (required_type is None or type_ == required_type)
