from __future__ import annotations

from dataclasses import dataclass
from typing import Any
from urllib.parse import parse_qs, quote, urlencode, urlparse

from flow_xhs.contracts.command_rules import NOTE_XSEC_SOURCE_VALUES, USER_XSEC_SOURCE_VALUES

_REDNOTE_HOSTS = {"www.xiaohongshu.com", "xiaohongshu.com"}
_NOTE_PATH_PREFIX = "/explore/"
_USER_PATH_PREFIX = "/user/profile/"


@dataclass(frozen=True)
class ParsedNoteUrl:
    note_id: str
    xsec_token: str | None
    xsec_source: str


@dataclass(frozen=True)
class ParsedUserUrl:
    user_id: str
    xsec_token: str | None
    xsec_source: str


def parse_note_url(url: str) -> ParsedNoteUrl:
    parsed = _parse_rednote_url(url=url, expected_path_prefix=_NOTE_PATH_PREFIX, kind="note")
    xsec_source = _normalize_source(
        parsed.query.get("xsec_source", ["pc_feed"])[0],
        allowed_values=NOTE_XSEC_SOURCE_VALUES,
        field_name="xsec_source",
    )
    return ParsedNoteUrl(
        note_id=parsed.resource_id,
        xsec_token=_normalize_token(parsed.query.get("xsec_token", [None])[0]),
        xsec_source=xsec_source,
    )


def parse_user_url(url: str) -> ParsedUserUrl:
    parsed = _parse_rednote_url(url=url, expected_path_prefix=_USER_PATH_PREFIX, kind="user")
    xsec_source = _normalize_source(
        parsed.query.get("xsec_source", ["pc_feed"])[0],
        allowed_values=USER_XSEC_SOURCE_VALUES,
        field_name="xsec_source",
    )
    return ParsedUserUrl(
        user_id=parsed.resource_id,
        xsec_token=_normalize_token(parsed.query.get("xsec_token", [None])[0]),
        xsec_source=xsec_source,
    )


def build_note_url(
    note_id: str,
    *,
    xsec_token: str | None = None,
    xsec_source: str | None = "pc_feed",
) -> str:
    clean_note_id = _normalize_required_id(note_id, field_name="note_id")
    clean_source = _normalize_source(
        xsec_source,
        allowed_values=NOTE_XSEC_SOURCE_VALUES,
        field_name="xsec_source",
        default="pc_feed",
    )
    return _build_url(
        path_prefix=_NOTE_PATH_PREFIX,
        resource_id=clean_note_id,
        xsec_token=xsec_token,
        xsec_source=clean_source,
    )


def build_user_url(
    user_id: str,
    *,
    xsec_token: str | None = None,
    xsec_source: str | None = "pc_feed",
) -> str:
    clean_user_id = _normalize_required_id(user_id, field_name="user_id")
    clean_source = _normalize_source(
        xsec_source,
        allowed_values=USER_XSEC_SOURCE_VALUES,
        field_name="xsec_source",
        default="pc_feed",
    )
    return _build_url(
        path_prefix=_USER_PATH_PREFIX,
        resource_id=clean_user_id,
        xsec_token=xsec_token,
        xsec_source=clean_source,
    )


def enrich_note_cards(items: list[dict[str, Any]], *, default_xsec_source: str) -> list[dict[str, Any]]:
    return [_with_note_url(item, default_xsec_source=default_xsec_source) for item in items]


def enrich_user_cards(items: list[dict[str, Any]], *, default_xsec_source: str) -> list[dict[str, Any]]:
    return [_with_user_url(item, default_xsec_source=default_xsec_source) for item in items]


def enrich_note_detail(
    item: dict[str, Any],
    *,
    xsec_token: str | None,
    xsec_source: str,
) -> dict[str, Any]:
    enriched = _with_note_url(item, default_xsec_source=xsec_source, fallback_xsec_token=xsec_token)
    user = enriched.get("user")
    if isinstance(user, dict):
        enriched["user"] = _with_user_url(user, default_xsec_source="pc_note")
    return enriched


def enrich_user_detail(
    item: dict[str, Any],
    *,
    xsec_token: str | None,
    xsec_source: str,
) -> dict[str, Any]:
    enriched = _with_user_url(item, default_xsec_source=xsec_source, fallback_xsec_token=xsec_token)
    notes = enriched.get("notes")
    if isinstance(notes, list):
        enriched["notes"] = [
            _with_note_url(note, default_xsec_source="pc_user") if isinstance(note, dict) else note
            for note in notes
        ]
    return enriched


@dataclass(frozen=True)
class _ParsedResourceUrl:
    resource_id: str
    query: dict[str, list[str]]


def _parse_rednote_url(*, url: str, expected_path_prefix: str, kind: str) -> _ParsedResourceUrl:
    if not isinstance(url, str):
        raise ValueError(f"{kind}_url 必须是字符串")
    raw = url.strip()
    if not raw:
        raise ValueError(f"{kind}_url 不能为空")

    parsed = urlparse(raw)
    if parsed.scheme not in {"http", "https"}:
        raise ValueError(f"{kind}_url 必须是 http(s) 链接")
    if parsed.netloc.lower() not in _REDNOTE_HOSTS:
        raise ValueError(f"{kind}_url 必须是 xiaohongshu.com 链接")
    if not parsed.path.startswith(expected_path_prefix):
        raise ValueError(f"{kind}_url 路径不合法")

    resource_id = parsed.path[len(expected_path_prefix):].strip("/")
    if not resource_id or "/" in resource_id:
        raise ValueError(f"{kind}_url 缺少合法 ID")

    return _ParsedResourceUrl(resource_id=resource_id, query=parse_qs(parsed.query, keep_blank_values=False))


def _build_url(*, path_prefix: str, resource_id: str, xsec_token: str | None, xsec_source: str) -> str:
    query_items: list[tuple[str, str]] = []
    clean_token = _normalize_token(xsec_token)
    if clean_token:
        query_items.append(("xsec_token", clean_token))
    query_items.append(("xsec_source", xsec_source))
    query = urlencode(query_items)
    return f"https://www.xiaohongshu.com{path_prefix}{quote(resource_id, safe='')}?{query}"


def _normalize_required_id(value: str, *, field_name: str) -> str:
    if not isinstance(value, str):
        raise ValueError(f"{field_name} 必须是字符串")
    text = value.strip()
    if not text:
        raise ValueError(f"{field_name} 不能为空")
    return text


def _normalize_token(value: str | None) -> str | None:
    if value is None:
        return None
    if not isinstance(value, str):
        raise ValueError("xsec_token 必须是字符串")
    text = value.strip()
    return text or None


def _normalize_source(
    value: str | None,
    *,
    allowed_values: tuple[str, ...],
    field_name: str,
    default: str | None = None,
) -> str:
    if value is None:
        if default is None:
            raise ValueError(f"{field_name} 不能为空")
        return default
    if not isinstance(value, str):
        raise ValueError(f"{field_name} 必须是字符串")
    text = value.strip().lower()
    if not text:
        if default is None:
            raise ValueError(f"{field_name} 不能为空")
        return default
    if text not in allowed_values:
        raise ValueError(f"{field_name} 不支持 '{text}'，可选值: {', '.join(allowed_values)}")
    return text


def _with_note_url(
    item: dict[str, Any],
    *,
    default_xsec_source: str,
    fallback_xsec_token: str | None = None,
) -> dict[str, Any]:
    enriched = dict(item)
    note_id = _first_string(enriched.get("note_id"), enriched.get("id"))
    if not note_id:
        return enriched
    xsec_token = _first_string(enriched.get("xsec_token"), fallback_xsec_token)
    xsec_source = _coerce_source_for_output(
        _first_string(enriched.get("xsec_source"), default_xsec_source),
        allowed_values=NOTE_XSEC_SOURCE_VALUES,
        default=default_xsec_source,
    )
    enriched["note_url"] = build_note_url(note_id, xsec_token=xsec_token, xsec_source=xsec_source)
    return enriched


def _with_user_url(
    item: dict[str, Any],
    *,
    default_xsec_source: str,
    fallback_xsec_token: str | None = None,
) -> dict[str, Any]:
    enriched = dict(item)
    user_id = _first_string(enriched.get("user_id"), enriched.get("id"))
    if not user_id:
        return enriched
    xsec_token = _first_string(enriched.get("xsec_token"), fallback_xsec_token)
    xsec_source = _coerce_source_for_output(
        _first_string(enriched.get("xsec_source"), default_xsec_source),
        allowed_values=USER_XSEC_SOURCE_VALUES,
        default=default_xsec_source,
    )
    enriched["user_url"] = build_user_url(user_id, xsec_token=xsec_token, xsec_source=xsec_source)
    return enriched


def _first_string(*values: Any) -> str | None:
    for value in values:
        if isinstance(value, str):
            text = value.strip()
            if text:
                return text
    return None


def _coerce_source_for_output(value: str | None, *, allowed_values: tuple[str, ...], default: str) -> str:
    if not value:
        return default
    text = value.strip().lower()
    if text in allowed_values:
        return text
    return default
