from __future__ import annotations

from typing import Any

from fastapi import APIRouter, Depends, HTTPException, Query, Security, status
from sqlalchemy.ext.asyncio import AsyncSession

from app.services.system_settings_service import (
    ReadOnlySettingError,
    bot_configuration_service,
)

from ..dependencies import get_db_session, require_api_token
from ..schemas.config import (
    SettingCategoryRef,
    SettingCategorySummary,
    SettingChoice,
    SettingDefinition,
    SettingUpdateRequest,
)


router = APIRouter()


def _coerce_value(key: str, value: Any) -> Any:
    definition = bot_configuration_service.get_definition(key)

    if value is None:
        if definition.is_optional:
            return None
        raise HTTPException(status.HTTP_400_BAD_REQUEST, 'Value is required')

    python_type = definition.python_type

    try:
        if python_type is bool:
            if isinstance(value, bool):
                normalized = value
            elif isinstance(value, str):
                lowered = value.strip().lower()
                if lowered in {'true', '1', 'yes', 'on', 'да'}:
                    normalized = True
                elif lowered in {'false', '0', 'no', 'off', 'нет'}:
                    normalized = False
                else:
                    raise ValueError('invalid bool')
            else:
                raise ValueError('invalid bool')

        elif python_type is int:
            normalized = int(value)
        elif python_type is float:
            normalized = float(value)
        else:
            normalized = str(value)
    except ValueError:
        raise HTTPException(status.HTTP_400_BAD_REQUEST, 'Invalid value type') from None

    choices = bot_configuration_service.get_choice_options(key)
    if choices:
        allowed_values = {option.value for option in choices}
        if normalized not in allowed_values:
            readable = ', '.join(bot_configuration_service.format_value(opt.value) for opt in choices)
            raise HTTPException(
                status.HTTP_400_BAD_REQUEST,
                detail=f'Value must be one of: {readable}',
            )

    return normalized


def _serialize_definition(definition, include_choices: bool = True) -> SettingDefinition:
    current = bot_configuration_service.get_current_value(definition.key)
    original = bot_configuration_service.get_original_value(definition.key)
    has_override = bot_configuration_service.has_override(definition.key)

    choices: list[SettingChoice] = []
    if include_choices:
        choices = [
            SettingChoice(
                value=option.value,
                label=option.label,
                description=option.description,
            )
            for option in bot_configuration_service.get_choice_options(definition.key)
        ]

    return SettingDefinition(
        key=definition.key,
        name=definition.display_name,
        category=SettingCategoryRef(
            key=definition.category_key,
            label=definition.category_label,
        ),
        type=definition.type_label,
        is_optional=definition.is_optional,
        current=current,
        original=original,
        has_override=has_override,
        read_only=bot_configuration_service.is_read_only(definition.key),
        choices=choices,
    )


@router.get('/categories', response_model=list[SettingCategorySummary])
async def list_categories(
    _: object = Security(require_api_token),
) -> list[SettingCategorySummary]:
    categories = bot_configuration_service.get_categories()
    return [SettingCategorySummary(key=key, label=label, items=count) for key, label, count in categories]


@router.get('', response_model=list[SettingDefinition])
async def list_settings(
    _: object = Security(require_api_token),
    category: str | None = Query(default=None, alias='category_key'),
) -> list[SettingDefinition]:
    items: list[SettingDefinition] = []
    if category:
        definitions = bot_configuration_service.get_settings_for_category(category)
        items.extend(_serialize_definition(defn) for defn in definitions)
        return items

    for category_key, _, _ in bot_configuration_service.get_categories():
        definitions = bot_configuration_service.get_settings_for_category(category_key)
        items.extend(_serialize_definition(defn) for defn in definitions)

    return items


@router.get('/{key}', response_model=SettingDefinition)
async def get_setting(
    key: str,
    _: object = Security(require_api_token),
) -> SettingDefinition:
    try:
        definition = bot_configuration_service.get_definition(key)
    except KeyError as error:  # pragma: no cover - защита от некорректного ключа
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Setting not found') from error

    return _serialize_definition(definition)


@router.put('/{key}', response_model=SettingDefinition)
async def update_setting(
    key: str,
    payload: SettingUpdateRequest,
    _: object = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> SettingDefinition:
    try:
        definition = bot_configuration_service.get_definition(key)
    except KeyError as error:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Setting not found') from error

    value = _coerce_value(key, payload.value)
    try:
        await bot_configuration_service.set_value(db, key, value)
    except ReadOnlySettingError as error:
        raise HTTPException(status.HTTP_403_FORBIDDEN, str(error)) from error
    await db.commit()

    return _serialize_definition(definition)


@router.delete('/{key}', response_model=SettingDefinition)
async def reset_setting(
    key: str,
    _: object = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> SettingDefinition:
    try:
        definition = bot_configuration_service.get_definition(key)
    except KeyError as error:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Setting not found') from error

    try:
        await bot_configuration_service.reset_value(db, key)
    except ReadOnlySettingError as error:
        raise HTTPException(status.HTTP_403_FORBIDDEN, str(error)) from error
    await db.commit()
    return _serialize_definition(definition)
