from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from fastapi import HTTPException, status
from typing import Optional
from . import repository
from .schema import CalculationCreate, CalculationUpdate, CalculationbreakdownCreate, CalculationbreakdownUpdate
from .models import Calculationbreakdown


def create_calculation(db: Session, data: CalculationCreate) -> dict:
    data_dict = data.model_dump()
    try:
        obj = repository.create(db, data_dict)
        db.commit()
        db.refresh(obj)
        return obj
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "unique" in msg or "duplicate" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Calculation already exists.")
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Referenced resource does not exist.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def get_calculation(db: Session, calculation_id: str):
    obj = repository.get_by_id(db, calculation_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Calculation not found.")
    return obj


def get_calculation_details(db: Session, calculation_id: str):
    obj = repository.get_with_details(db, calculation_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Calculation not found.")
    return obj


def list_calculations(db: Session, limit: int, offset: int, session_id: Optional[str] = None) -> dict:
    filters = {}
    if session_id is not None:
        filters["session_id"] = session_id
    items = repository.list_all(db, limit=limit, offset=offset, **filters)
    total = repository.count_all(db, **filters)
    return {"items": items, "total": total, "limit": limit, "offset": offset}


def update_calculation(db: Session, calculation_id: str, data: CalculationUpdate):
    data_dict = data.model_dump(exclude_unset=True)
    if not data_dict:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="No fields to update.")
    try:
        obj = repository.update(db, calculation_id, data_dict)
        if not obj:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Calculation not found.")
        db.commit()
        db.refresh(obj)
        return obj
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "unique" in msg or "duplicate" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Calculation already exists.")
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Referenced resource does not exist.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def delete_calculation(db: Session, calculation_id: str):
    try:
        success = repository.delete(db, calculation_id)
        if not success:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Calculation not found.")
        db.commit()
        return {"detail": "Calculation deleted successfully."}
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def list_breakdowns_by_calculation(db: Session, calculation_id: str):
    calc = repository.get_by_id(db, calculation_id)
    if not calc:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Calculation not found.")
    items = db.query(Calculationbreakdown).filter(Calculationbreakdown.calculation_id == calculation_id).order_by(Calculationbreakdown.year.asc()).all()
    return items


def create_breakdown(db: Session, data: CalculationbreakdownCreate):
    data_dict = data.model_dump()
    calc = repository.get_by_id(db, data_dict["calculation_id"])
    if not calc:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Calculation does not exist.")
    try:
        obj = Calculationbreakdown(**data_dict)
        db.add(obj)
        db.flush()
        db.commit()
        db.refresh(obj)
        return obj
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "unique" in msg or "duplicate" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Breakdown already exists.")
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Referenced resource does not exist.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def get_breakdown(db: Session, breakdown_id: str):
    obj = db.query(Calculationbreakdown).filter(Calculationbreakdown.id == breakdown_id).first()
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Breakdown not found.")
    return obj


def update_breakdown(db: Session, breakdown_id: str, data: CalculationbreakdownUpdate):
    data_dict = data.model_dump(exclude_unset=True)
    if not data_dict:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="No fields to update.")
    try:
        obj = db.query(Calculationbreakdown).filter(Calculationbreakdown.id == breakdown_id).first()
        if not obj:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Breakdown not found.")
        for key, value in data_dict.items():
            setattr(obj, key, value)
        db.flush()
        db.commit()
        db.refresh(obj)
        return obj
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "unique" in msg or "duplicate" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Breakdown already exists.")
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Referenced resource does not exist.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def delete_breakdown(db: Session, breakdown_id: str):
    try:
        obj = db.query(Calculationbreakdown).filter(Calculationbreakdown.id == breakdown_id).first()
        if not obj:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Breakdown not found.")
        db.delete(obj)
        db.flush()
        db.commit()
        return {"detail": "Breakdown deleted successfully."}
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise