from __future__ import annotations

from typing import Any

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

from app.database.crud.promo_group import (
    count_promo_group_members,
    count_promo_groups,
    create_promo_group,
    delete_promo_group,
    get_promo_group_by_id,
    get_promo_groups_with_counts,
    update_promo_group,
)
from app.database.models import PromoGroup

from ..dependencies import get_db_session, require_api_token
from ..schemas.promo_groups import (
    PromoGroupCreateRequest,
    PromoGroupListResponse,
    PromoGroupResponse,
    PromoGroupUpdateRequest,
)


router = APIRouter()


def _normalize_period_discounts(group: PromoGroup) -> dict[int, int]:
    raw = group.period_discounts or {}
    normalized: dict[int, int] = {}
    if isinstance(raw, dict):
        for key, value in raw.items():
            try:
                normalized[int(key)] = int(value)
            except (TypeError, ValueError):
                continue
    return normalized


def _serialize(group: PromoGroup, members_count: int = 0) -> PromoGroupResponse:
    return PromoGroupResponse(
        id=group.id,
        name=group.name,
        server_discount_percent=group.server_discount_percent,
        traffic_discount_percent=group.traffic_discount_percent,
        device_discount_percent=group.device_discount_percent,
        period_discounts=_normalize_period_discounts(group),
        auto_assign_total_spent_kopeks=group.auto_assign_total_spent_kopeks,
        apply_discounts_to_addons=group.apply_discounts_to_addons,
        is_default=group.is_default,
        members_count=members_count,
        created_at=getattr(group, 'created_at', None),
        updated_at=getattr(group, 'updated_at', None),
    )


@router.get('', response_model=PromoGroupListResponse, response_model_exclude_none=True)
async def list_promo_groups(
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
    limit: int = Query(50, ge=1, le=200),
    offset: int = Query(0, ge=0),
) -> PromoGroupListResponse:
    total = await count_promo_groups(db)
    groups_with_counts = await get_promo_groups_with_counts(
        db,
        offset=offset,
        limit=limit,
    )

    return PromoGroupListResponse(
        items=[_serialize(group, members_count=count) for group, count in groups_with_counts],
        total=total,
        limit=limit,
        offset=offset,
    )


@router.get('/{group_id}', response_model=PromoGroupResponse, response_model_exclude_none=True)
async def get_promo_group(
    group_id: int,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> PromoGroupResponse:
    group = await get_promo_group_by_id(db, group_id)
    if not group:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Promo group not found')

    members_count = await count_promo_group_members(db, group_id)
    return _serialize(group, members_count=members_count)


@router.post(
    '',
    response_model=PromoGroupResponse,
    response_model_exclude_none=True,
    status_code=status.HTTP_201_CREATED,
)
async def create_promo_group_endpoint(
    payload: PromoGroupCreateRequest,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> PromoGroupResponse:
    try:
        group = await create_promo_group(
            db,
            name=payload.name,
            server_discount_percent=payload.server_discount_percent,
            traffic_discount_percent=payload.traffic_discount_percent,
            device_discount_percent=payload.device_discount_percent,
            period_discounts=payload.period_discounts,
            auto_assign_total_spent_kopeks=payload.auto_assign_total_spent_kopeks,
            apply_discounts_to_addons=payload.apply_discounts_to_addons,
            is_default=payload.is_default,
        )
    except IntegrityError as exc:
        await db.rollback()
        raise HTTPException(
            status.HTTP_400_BAD_REQUEST,
            'Promo group with this name already exists',
        ) from exc
    return _serialize(group, members_count=0)


@router.patch(
    '/{group_id}',
    response_model=PromoGroupResponse,
    response_model_exclude_none=True,
)
async def update_promo_group_endpoint(
    group_id: int,
    payload: PromoGroupUpdateRequest,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> PromoGroupResponse:
    group = await get_promo_group_by_id(db, group_id)
    if not group:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Promo group not found')

    try:
        group = await update_promo_group(
            db,
            group,
            name=payload.name,
            server_discount_percent=payload.server_discount_percent,
            traffic_discount_percent=payload.traffic_discount_percent,
            device_discount_percent=payload.device_discount_percent,
            period_discounts=payload.period_discounts,
            auto_assign_total_spent_kopeks=payload.auto_assign_total_spent_kopeks,
            apply_discounts_to_addons=payload.apply_discounts_to_addons,
            is_default=payload.is_default,
        )
    except IntegrityError as exc:
        await db.rollback()
        raise HTTPException(
            status.HTTP_400_BAD_REQUEST,
            'Promo group with this name already exists',
        ) from exc
    members_count = await count_promo_group_members(db, group_id)
    return _serialize(group, members_count=members_count)


@router.delete('/{group_id}', status_code=status.HTTP_204_NO_CONTENT)
async def delete_promo_group_endpoint(
    group_id: int,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> Response:
    group = await get_promo_group_by_id(db, group_id)
    if not group:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Promo group not found')

    success = await delete_promo_group(db, group)
    if not success:
        raise HTTPException(status.HTTP_400_BAD_REQUEST, 'Cannot delete default promo group')

    return Response(status_code=status.HTTP_204_NO_CONTENT)
