"""Profile CRUD + 字段校验。

Profile 字段:
    gender  f / m / nb                          （唯一"不可空"的字段）
    age     18-99 整数                          （唯一"不可空"的字段）
    city    ≤ 30 字符，STR_RE 允许             （唯一"不可空"的字段）
    tags    ≤ 20 个，每个 ≤ 20 字符 STR_RE      （可空）
    bio     ≤ 500 字符                          （可空）

tags 在 DB 以 `|a|b|c|` 形式存（便于 LIKE `%|tag|%`）；业务层 <-> list[str]。
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Any

from .db import get_conn, now_ts

TAG_MAX_LEN = 20
TAGS_MAX_COUNT = 20
CITY_MAX_LEN = 30
BIO_MAX_LEN = 500
STR_RE = re.compile(r"^[a-zA-Z0-9_一-龥-]+$")

ALLOWED_GENDERS = {"f", "m", "nb"}


class ProfileError(Exception):
    def __init__(self, code: str, msg: str):
        super().__init__(msg)
        self.code = code


@dataclass
class Profile:
    user_id: str
    gender: str | None
    age: int | None
    city: str | None
    tags: list[str]
    bio: str | None
    updated_at: int


def get(user_id: str) -> Profile:
    with get_conn() as conn:
        row = conn.execute(
            "SELECT user_id, gender, age, city, tags, bio, updated_at "
            "FROM profiles WHERE user_id = ?",
            (user_id,),
        ).fetchone()
    if row is None:
        raise ProfileError("profile_missing", "profile 未初始化（请先 register/verify）")
    return _row_to_profile(row)


def update(user_id: str, fields: dict[str, Any]) -> Profile:
    """部分更新。fields 里允许：gender / age / city / tags / bio。"""
    normalized: dict[str, Any] = {}

    if "gender" in fields:
        g = fields["gender"]
        if g is not None:
            if not isinstance(g, str) or g not in ALLOWED_GENDERS:
                raise ProfileError(
                    "invalid_value", f"gender 必须 f/m/nb，收到 {g!r}"
                )
        normalized["gender"] = g

    if "age" in fields:
        a = fields["age"]
        if a is not None:
            if not isinstance(a, int) or a < 18 or a > 99:
                raise ProfileError("invalid_value", f"age 范围 [18, 99]，收到 {a!r}")
        normalized["age"] = a

    if "city" in fields:
        c = fields["city"]
        if c is not None:
            if not isinstance(c, str) or len(c) > CITY_MAX_LEN:
                raise ProfileError(
                    "invalid_value", f"city 长度 ≤ {CITY_MAX_LEN}"
                )
            if not STR_RE.match(c):
                raise ProfileError("invalid_value", f"city 包含非法字符：{c!r}")
        normalized["city"] = c

    if "tags" in fields:
        t = fields["tags"]
        if t is None:
            normalized["tags"] = None
        else:
            if not isinstance(t, list):
                raise ProfileError("invalid_value", "tags 必须是列表")
            if len(t) > TAGS_MAX_COUNT:
                raise ProfileError(
                    "invalid_value", f"tags 个数 ≤ {TAGS_MAX_COUNT}"
                )
            cleaned: list[str] = []
            for tag in t:
                if not isinstance(tag, str):
                    raise ProfileError("invalid_value", f"tag 必须是字符串：{tag!r}")
                tag = tag.strip()
                if not tag:
                    continue
                if len(tag) > TAG_MAX_LEN:
                    raise ProfileError(
                        "invalid_value", f"tag 长度 ≤ {TAG_MAX_LEN}：{tag!r}"
                    )
                if not STR_RE.match(tag):
                    raise ProfileError("invalid_value", f"tag 非法字符：{tag!r}")
                if tag not in cleaned:
                    cleaned.append(tag)
            normalized["tags"] = _tags_to_db(cleaned)

    if "bio" in fields:
        b = fields["bio"]
        if b is not None:
            if not isinstance(b, str) or len(b) > BIO_MAX_LEN:
                raise ProfileError("invalid_value", f"bio 长度 ≤ {BIO_MAX_LEN}")
        normalized["bio"] = b

    if not normalized:
        raise ProfileError("invalid_value", "没有任何可更新字段")

    normalized["updated_at"] = now_ts()

    set_clause = ", ".join(f"{k} = ?" for k in normalized)
    params = list(normalized.values()) + [user_id]
    with get_conn() as conn:
        conn.execute(
            f"UPDATE profiles SET {set_clause} WHERE user_id = ?",
            params,
        )
        row = conn.execute(
            "SELECT user_id, gender, age, city, tags, bio, updated_at "
            "FROM profiles WHERE user_id = ?",
            (user_id,),
        ).fetchone()
    return _row_to_profile(row)


def _row_to_profile(row) -> Profile:
    return Profile(
        user_id=row["user_id"],
        gender=row["gender"],
        age=row["age"],
        city=row["city"],
        tags=_tags_from_db(row["tags"]),
        bio=row["bio"],
        updated_at=row["updated_at"],
    )


def _tags_to_db(tags: list[str]) -> str | None:
    if not tags:
        return None
    return "|" + "|".join(tags) + "|"


def _tags_from_db(raw: str | None) -> list[str]:
    if not raw:
        return []
    # "|a|b|c|" → ["a","b","c"]
    return [t for t in raw.strip("|").split("|") if t]
