from __future__ import annotations

import json
import re
from dataclasses import asdict, dataclass

from src.config.naming_config import NamingConfig
from src.errors import UserError
from src.naming.slugify import slug_tokens, truncate_tokens


_HOUSE_NUMBER_RE = re.compile(r"^\d{1,4}[a-z]{0,2}$")
_POSTCODE_RE = re.compile(r"^\d{4}[a-z]{2}$")

_STREET_SUFFIXES = (
    "straat",
    "laan",
    "weg",
    "plein",
    "gracht",
    "dijk",
    "kade",
    "hof",
    "boulevard",
    "singel",
    "park",
    "allee",
    "steeg",
    "pad",
    "markt",
)


@dataclass(frozen=True)
class NamingPreview:
    filename: str
    place_slug: str
    venue_slug: str | None
    event_slug: str | None
    venue_dropped_reason: str | None
    event_dropped_reason: str | None

    def to_json(self) -> str:
        return json.dumps(asdict(self), ensure_ascii=True, sort_keys=True)


def build_filename(
    *,
    place: str,
    venue: str | None,
    event: str | None,
    config: NamingConfig,
) -> NamingPreview:
    place_tokens = truncate_tokens(slug_tokens(place), config.slug_max_len)
    if not place_tokens:
        raise UserError("Place is required for filename generation.")

    venue_tokens, venue_reason = _component_tokens(venue, config)
    event_tokens, event_reason = _component_tokens(event, config)

    all_tokens = _dedupe_tokens(place_tokens + venue_tokens + event_tokens)
    all_tokens = truncate_tokens(all_tokens, config.slug_max_len)
    filename = "-".join(all_tokens)

    return NamingPreview(
        filename=filename,
        place_slug="-".join(place_tokens),
        venue_slug="-".join(venue_tokens) if venue_tokens else None,
        event_slug="-".join(event_tokens) if event_tokens else None,
        venue_dropped_reason=venue_reason,
        event_dropped_reason=event_reason,
    )


def _component_tokens(value: str | None, config: NamingConfig) -> tuple[list[str], str | None]:
    if value is None or not str(value).strip():
        return ([], "empty")
    tokens = truncate_tokens(slug_tokens(value), config.slug_max_len)
    if not tokens:
        return ([], "empty")
    if _contains_denylist(tokens, config.denylist_terms):
        return ([], "denylist")
    filtered = [t for t in tokens if not _is_address_like_token(t)]
    if not filtered:
        return ([], "address_only")
    if len(filtered) != len(tokens):
        return (filtered, None)
    return (tokens, None)


def _contains_denylist(tokens: list[str], denylist_terms: list[str]) -> bool:
    terms = [t.strip().lower() for t in denylist_terms if t.strip()]
    if not terms:
        return False
    for tok in tokens:
        for term in terms:
            if term in tok:
                return True
    return False


def _is_address_like_token(token: str) -> bool:
    if _HOUSE_NUMBER_RE.match(token) or _POSTCODE_RE.match(token):
        return True
    for suffix in _STREET_SUFFIXES:
        if token == suffix or token.endswith(suffix):
            return True
    return False


def _dedupe_tokens(tokens: list[str]) -> list[str]:
    seen: set[str] = set()
    out: list[str] = []
    for tok in tokens:
        if tok in seen:
            continue
        seen.add(tok)
        out.append(tok)
    return out
