from __future__ import annotations

import json
from typing import Any


ETHNICITY_FILTER_CHOICES = [
    "any",
    "european",
    "mediterranean_mena",
    "latina",
    "east_asian",
    "southeast_asian",
    "south_asian",
    "black_african",
    "indigenous",
    "mixed",
    "asian",
    "white_asian",
    "western_european",
    "french_european",
    "germanic_european",
    "nordic_european",
    "celtic_european",
    "slavic_european",
    "baltic_european",
    "alpine_european",
    "balkan_european",
    "greek_mediterranean",
    "italian_mediterranean",
    "iberian_mediterranean",
]
ETHNICITY_LIST_KEYS = tuple(choice for choice in ETHNICITY_FILTER_CHOICES if choice != "any")
ETHNICITY_BASE_LIST_KEYS = (
    "european",
    "mediterranean_mena",
    "latina",
    "east_asian",
    "southeast_asian",
    "south_asian",
    "black_african",
    "indigenous",
    "mixed",
)
EUROPEAN_REGIONAL_LIST_KEYS = (
    "western_european",
    "french_european",
    "germanic_european",
    "nordic_european",
    "celtic_european",
    "slavic_european",
    "baltic_european",
    "alpine_european",
    "balkan_european",
)
MEDITERRANEAN_REGIONAL_LIST_KEYS = (
    "greek_mediterranean",
    "italian_mediterranean",
    "iberian_mediterranean",
)
ETHNICITY_RANDOM_TOKENS = {"", "random", "auto", "global", "from_global", "default"}


def ethnicity_text_from_value(value: Any) -> str:
    if isinstance(value, dict):
        return str(value.get("ethnicity") or "").strip()
    text = str(value or "").strip()
    if not text:
        return ""
    if text.startswith("{"):
        try:
            raw = json.loads(text)
        except json.JSONDecodeError:
            return text
        if isinstance(raw, dict):
            return str(raw.get("ethnicity") or "").strip()
    return text


def is_valid_ethnicity_filter(value: Any) -> bool:
    text = ethnicity_text_from_value(value)
    return text == "any" or text in ETHNICITY_FILTER_CHOICES or "+" in text


def normalize_ethnicity_filter(value: Any, default: str = "any", allow_random: bool = False) -> str:
    text = ethnicity_text_from_value(value)
    if text.lower() in ETHNICITY_RANDOM_TOKENS:
        return "random" if allow_random else default
    return text if is_valid_ethnicity_filter(text) else default


def build_filter_config_json(
    ethnicity: str = "any",
    figure: str = "curvy",
    no_plus_women: bool = False,
    no_black: bool = False,
    include_european: bool = True,
    include_mediterranean_mena: bool = True,
    include_latina: bool = True,
    include_east_asian: bool = True,
    include_southeast_asian: bool = True,
    include_south_asian: bool = True,
    include_black_african: bool = True,
    include_indigenous: bool = True,
    include_mixed: bool = True,
    include_plus_size: bool = True,
) -> str:
    include_flags = {
        "european": include_european,
        "mediterranean_mena": include_mediterranean_mena,
        "latina": include_latina,
        "east_asian": include_east_asian,
        "southeast_asian": include_southeast_asian,
        "south_asian": include_south_asian,
        "black_african": include_black_african,
        "indigenous": include_indigenous,
        "mixed": include_mixed,
    }
    selected_ethnicities = [key for key, enabled in include_flags.items() if enabled]
    disabled_ethnicities = [key for key, enabled in include_flags.items() if not enabled]
    enabled_ethnicities = list(selected_ethnicities)
    if enabled_ethnicities:
        enabled_ethnicities.extend(f"exclude_{key}" for key in disabled_ethnicities)
    if 0 < len(selected_ethnicities) < len(include_flags):
        ethnicity = "+".join(enabled_ethnicities)
    elif not is_valid_ethnicity_filter(ethnicity):
        ethnicity = "any"
    return json.dumps(
        {
            "ethnicity": ethnicity,
            "ethnicity_includes": selected_ethnicities,
            "figure": figure if figure in ("curvy", "balanced", "bombshell", "random") else "curvy",
            "include_plus_size": bool(include_plus_size),
            "include_black_african": bool(include_black_african),
            "no_plus_women": not bool(include_plus_size) or bool(no_plus_women),
            "no_black": not bool(include_black_african) or bool(no_black),
        },
        ensure_ascii=True,
        sort_keys=True,
    )


def build_ethnicity_list_json(
    include_european: bool = False,
    include_mediterranean_mena: bool = False,
    include_latina: bool = False,
    include_east_asian: bool = False,
    include_southeast_asian: bool = False,
    include_south_asian: bool = False,
    include_black_african: bool = False,
    include_indigenous: bool = False,
    include_mixed: bool = False,
    include_asian: bool = False,
    include_white_asian: bool = False,
    include_western_european: bool = False,
    include_french_european: bool = False,
    include_germanic_european: bool = False,
    include_nordic_european: bool = False,
    include_celtic_european: bool = False,
    include_slavic_european: bool = False,
    include_baltic_european: bool = False,
    include_alpine_european: bool = False,
    include_balkan_european: bool = False,
    include_greek_mediterranean: bool = False,
    include_italian_mediterranean: bool = False,
    include_iberian_mediterranean: bool = False,
    strict_excludes: bool = True,
) -> dict[str, str]:
    include_flags = {
        "european": include_european,
        "mediterranean_mena": include_mediterranean_mena,
        "latina": include_latina,
        "east_asian": include_east_asian,
        "southeast_asian": include_southeast_asian,
        "south_asian": include_south_asian,
        "black_african": include_black_african,
        "indigenous": include_indigenous,
        "mixed": include_mixed,
        "asian": include_asian,
        "white_asian": include_white_asian,
        "western_european": include_western_european,
        "french_european": include_french_european,
        "germanic_european": include_germanic_european,
        "nordic_european": include_nordic_european,
        "celtic_european": include_celtic_european,
        "slavic_european": include_slavic_european,
        "baltic_european": include_baltic_european,
        "alpine_european": include_alpine_european,
        "balkan_european": include_balkan_european,
        "greek_mediterranean": include_greek_mediterranean,
        "italian_mediterranean": include_italian_mediterranean,
        "iberian_mediterranean": include_iberian_mediterranean,
    }
    selected = [key for key in ETHNICITY_LIST_KEYS if include_flags.get(key)]
    if not selected or set(selected) == set(ETHNICITY_LIST_KEYS):
        ethnicity = "any"
    else:
        tokens = list(selected)
        if strict_excludes:
            protected: set[str] = set()
            if "asian" in selected:
                protected.update(("east_asian", "southeast_asian", "south_asian"))
            if "white_asian" in selected:
                protected.update(("european", "east_asian", "southeast_asian", "south_asian", "mixed"))
            if any(key in selected for key in EUROPEAN_REGIONAL_LIST_KEYS):
                protected.add("european")
            if any(key in selected for key in MEDITERRANEAN_REGIONAL_LIST_KEYS):
                protected.add("mediterranean_mena")
            if "mixed" in selected:
                protected.update(ETHNICITY_BASE_LIST_KEYS)
            tokens.extend(
                f"exclude_{key}"
                for key in ETHNICITY_BASE_LIST_KEYS
                if key not in selected and key not in protected
            )
        ethnicity = "+".join(tokens)
    filter_config = {
        "ethnicity": ethnicity,
        "ethnicity_includes": selected,
    }
    summary = "any ethnicity" if ethnicity == "any" else "ethnicity list: " + ", ".join(selected)
    return {
        "ethnicity": ethnicity,
        "filter_config": json.dumps(filter_config, ensure_ascii=True, sort_keys=True),
        "summary": summary,
    }


def parse_filter_config(filter_config: str | dict[str, Any] | None) -> dict[str, Any]:
    defaults = {
        "ethnicity": "any",
        "figure": "curvy",
        "no_plus_women": False,
        "no_black": False,
        "include_plus_size": True,
        "include_black_african": True,
    }
    if not filter_config:
        return defaults
    if isinstance(filter_config, dict):
        raw = filter_config
    else:
        text = str(filter_config).strip()
        if not text.startswith("{"):
            raw = {"ethnicity": text}
        else:
            try:
                raw = json.loads(text)
            except json.JSONDecodeError as exc:
                raise ValueError(f"Invalid filter_config JSON: {exc}") from exc
    if not isinstance(raw, dict):
        raise ValueError("filter_config must be a JSON object")
    parsed = {**defaults, **raw}
    parsed["ethnicity"] = normalize_ethnicity_filter(parsed.get("ethnicity"), "any")
    parsed["figure"] = parsed["figure"] if parsed.get("figure") in ("curvy", "balanced", "bombshell", "random") else "curvy"
    parsed["include_plus_size"] = bool(parsed.get("include_plus_size"))
    parsed["include_black_african"] = bool(parsed.get("include_black_african"))
    parsed["no_plus_women"] = bool(parsed.get("no_plus_women"))
    parsed["no_black"] = bool(parsed.get("no_black"))
    return parsed


_ethnicity_text_from_value = ethnicity_text_from_value
_is_valid_ethnicity_filter = is_valid_ethnicity_filter
_parse_filter_config = parse_filter_config
