from collections.abc import Sequence

from fastapi.exceptions import RequestValidationError
from pydantic import ValidationError
from src.__types.error_responses import (
    ErrorCode,
    InvalidParameter,
)


def request_validation_error_to_invalid_params(exc: RequestValidationError | ValidationError) -> list[InvalidParameter]:
    """FastAPI の RequestValidationError または Pydantic の ValidationError から InvalidParameter リストへ変換する"""

    def _request_loc_to_name(loc: Sequence[str | int]) -> str:
        """RequestValidationError, ValidationError の loc を <prefix>[n].<segment> 形式のパラメータ名に変換する"""
        segments = list(loc)
        if len(segments) == 0:
            return ""

        # FastAPIの先頭セグメント(body/query/path/header/cookie)は除外
        if isinstance(segments[0], str) and segments[0] in {"body", "query", "path", "header", "cookie"}:
            segments = segments[1:]

        return _loc_to_name(tuple(segments), prefix="")

    return [
        InvalidParameter(
            name=_request_loc_to_name(error.get("loc", ())),
            code=_pydantic_type_to_error_code(error.get("type", "")),
        )
        for error in exc.errors()
    ]


def _loc_to_name(loc: tuple[str | int, ...], prefix: str) -> str:
    """Loc を <prefix>[n].<segment> 形式のパラメータ名に変換する"""
    parts: list[str] = []
    for segment in loc:
        if isinstance(segment, int):
            if parts:
                parts[-1] = f"{parts[-1]}[{segment}]"
            else:
                parts.append(f"{prefix}[{segment}]")
        else:
            parts.append(str(segment))
    if not parts:
        return prefix
    if prefix == "":
        return ".".join(parts)
    if parts[0] != prefix and not parts[0].startswith(f"{prefix}["):
        parts.insert(0, prefix)
    return ".".join(parts)


def _pydantic_type_to_error_code(error_type: str) -> ErrorCode:
    """Pydantic の エラー type に対応する ErrorCode を返す"""
    if error_type in ("missing", "string_too_short", "too_short"):
        return ErrorCode.REQUIRED
    if error_type == "too_long":
        return ErrorCode.TOO_MANY
    return ErrorCode.INVALID_FORMAT
