from datetime import datetime, timezone
from uuid import uuid4

from fastapi import HTTPException
from sqlmodel import Session, select

from app.models import Benefit, BenefitRedemption, BenefitRedemptionStatus, CoinTransaction, CoinTransactionType
from app.models.child import Child
from app.services.tasks import ledger_for_child


def _key(prefix: str, subject_id: int, idempotency_key: str | None) -> str:
    return f"{prefix}:{subject_id}:{idempotency_key or uuid4().hex}"


def _enum_value(value):
    return value.value if hasattr(value, "value") else value


def serialize_benefit(benefit: Benefit, available: int | None = None) -> dict:
    payload = {
        "id": benefit.id,
        "slug": benefit.slug,
        "title": benefit.title,
        "description": benefit.description,
        "price": benefit.price,
        "child_id": benefit.child_id,
        "active": benefit.active,
        "sort_order": benefit.sort_order,
    }
    if available is not None:
        payload["affordable"] = available >= benefit.price
    return payload


def serialize_redemption(session: Session, redemption: BenefitRedemption) -> dict:
    benefit = session.get(Benefit, redemption.benefit_id)
    child = session.get(Child, redemption.child_id)
    return {
        "id": redemption.id,
        "benefit_id": redemption.benefit_id,
        "benefit_title": benefit.title if benefit else "Unbekannte Belohnung",
        "child_id": redemption.child_id,
        "child_name": child.name if child else "Unbekanntes Kind",
        "status": _enum_value(redemption.status),
        "price": redemption.price,
        "requested_at": redemption.requested_at.isoformat(),
        "fulfilled_at": redemption.fulfilled_at.isoformat() if redemption.fulfilled_at else None,
        "cancelled_at": redemption.cancelled_at.isoformat() if redemption.cancelled_at else None,
    }


def list_benefits(session: Session, child_id: int | None = None) -> dict:
    available = None
    if child_id is not None:
        child = session.get(Child, child_id)
        if child is None:
            raise HTTPException(status_code=404, detail={"code": "child_not_found", "message": "Kind nicht gefunden"})
        available = ledger_for_child(session, child_id)["summary"]["available"]

    statement = select(Benefit).where(Benefit.active == True).order_by(Benefit.sort_order, Benefit.title)  # noqa: E712
    benefits = session.exec(statement).all()
    if child_id is not None:
        benefits = [item for item in benefits if item.child_id is None or item.child_id == child_id]
    return {"benefits": [serialize_benefit(item, available) for item in benefits]}


def list_redemptions(session: Session, child_id: int | None = None) -> dict:
    statement = select(BenefitRedemption).order_by(BenefitRedemption.requested_at.desc())
    if child_id is not None:
        statement = statement.where(BenefitRedemption.child_id == child_id)
    rows = session.exec(statement).all()
    return {"redemptions": [serialize_redemption(session, row) for row in rows]}


def redeem_benefit(session: Session, benefit_id: int, child_id: int, idempotency_key: str | None = None) -> dict:
    child = session.get(Child, child_id)
    if child is None:
        raise HTTPException(status_code=404, detail={"code": "child_not_found", "message": "Kind nicht gefunden"})
    benefit = session.get(Benefit, benefit_id)
    if benefit is None or not benefit.active or (benefit.child_id is not None and benefit.child_id != child_id):
        raise HTTPException(status_code=404, detail={"code": "benefit_not_found", "message": "Belohnung nicht gefunden"})

    redemption_key = _key("benefit:redeem", benefit_id, idempotency_key)
    existing = session.exec(
        select(BenefitRedemption).where(
            BenefitRedemption.benefit_id == benefit_id,
            BenefitRedemption.child_id == child_id,
            BenefitRedemption.idempotency_key == redemption_key,
        )
    ).first()
    if existing is not None:
        return {"redemption": serialize_redemption(session, existing), "idempotent": True}

    ledger = ledger_for_child(session, child_id)
    if ledger["summary"]["available"] < benefit.price:
        raise HTTPException(status_code=409, detail={"code": "insufficient_coins", "message": "Zu wenig verfügbare Coins"})

    redemption = BenefitRedemption(
        benefit_id=benefit.id,
        child_id=child_id,
        status=BenefitRedemptionStatus.reserved,
        price=benefit.price,
        idempotency_key=redemption_key,
    )
    session.add(redemption)
    session.flush()

    reserve_key = f"coin:benefit:reserve:{redemption.id}"
    session.add(
        CoinTransaction(
            child_id=child_id,
            transaction_type=CoinTransactionType.reserve,
            amount=0,
            reserved_amount=benefit.price,
            comment=f"Belohnung reserviert: {benefit.title}",
            benefit_redemption_id=redemption.id,
            idempotency_key=reserve_key,
        )
    )
    session.commit()
    session.refresh(redemption)
    return {"redemption": serialize_redemption(session, redemption), "idempotent": False}


def fulfill_redemption(session: Session, redemption_id: int, idempotency_key: str | None = None) -> dict:
    redemption = session.get(BenefitRedemption, redemption_id)
    if redemption is None:
        raise HTTPException(status_code=404, detail={"code": "redemption_not_found", "message": "Einlösung nicht gefunden"})
    if redemption.status == BenefitRedemptionStatus.fulfilled:
        return {"redemption": serialize_redemption(session, redemption), "idempotent": True}
    if redemption.status == BenefitRedemptionStatus.cancelled:
        raise HTTPException(status_code=409, detail={"code": "redemption_cancelled", "message": "Stornierte Einlösung kann nicht erhalten markiert werden"})

    capture_key = _key("benefit:capture", redemption_id, idempotency_key)
    existing_capture = session.exec(select(CoinTransaction).where(CoinTransaction.idempotency_key == capture_key)).first()
    if existing_capture is None:
        benefit = session.get(Benefit, redemption.benefit_id)
        session.add(
            CoinTransaction(
                child_id=redemption.child_id,
                transaction_type=CoinTransactionType.capture,
                amount=-redemption.price,
                reserved_amount=-redemption.price,
                comment=f"Belohnung erhalten: {benefit.title if benefit else 'Belohnung'}",
                benefit_redemption_id=redemption.id,
                idempotency_key=capture_key,
            )
        )
    redemption.status = BenefitRedemptionStatus.fulfilled
    redemption.fulfilled_at = datetime.now(timezone.utc)
    session.add(redemption)
    session.commit()
    session.refresh(redemption)
    return {"redemption": serialize_redemption(session, redemption), "idempotent": False}


def cancel_redemption(session: Session, redemption_id: int, idempotency_key: str | None = None) -> dict:
    redemption = session.get(BenefitRedemption, redemption_id)
    if redemption is None:
        raise HTTPException(status_code=404, detail={"code": "redemption_not_found", "message": "Einlösung nicht gefunden"})
    if redemption.status == BenefitRedemptionStatus.cancelled:
        return {"redemption": serialize_redemption(session, redemption), "idempotent": True}
    if redemption.status == BenefitRedemptionStatus.fulfilled:
        raise HTTPException(status_code=409, detail={"code": "redemption_fulfilled", "message": "Bereits erhaltene Belohnung kann nicht storniert werden"})

    release_key = _key("benefit:release", redemption_id, idempotency_key)
    existing_release = session.exec(select(CoinTransaction).where(CoinTransaction.idempotency_key == release_key)).first()
    if existing_release is None:
        benefit = session.get(Benefit, redemption.benefit_id)
        session.add(
            CoinTransaction(
                child_id=redemption.child_id,
                transaction_type=CoinTransactionType.release,
                amount=0,
                reserved_amount=-redemption.price,
                comment=f"Belohnung storniert: {benefit.title if benefit else 'Belohnung'}",
                benefit_redemption_id=redemption.id,
                idempotency_key=release_key,
            )
        )
    redemption.status = BenefitRedemptionStatus.cancelled
    redemption.cancelled_at = datetime.now(timezone.utc)
    session.add(redemption)
    session.commit()
    session.refresh(redemption)
    return {"redemption": serialize_redemption(session, redemption), "idempotent": False}
