from fastapi import APIRouter, HTTPException, Depends
from pydantic import ValidationError
from sqlalchemy.orm import Session, joinedload
from typing import List, Optional
from ..models import (
    Dewar as DewarModel,
    Slot as SlotModel,
    LogisticsEvent as LogisticsEventModel,
)
from ..schemas import LogisticsEventCreate, SlotSchema, Dewar as DewarSchema
from ..database import get_db
import logging
from datetime import datetime, timedelta

router = APIRouter()

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def calculate_time_until_refill(
    last_refill: Optional[datetime], refill_interval_hours: int = 1
) -> int:
    refill_interval = timedelta(hours=refill_interval_hours)
    now = datetime.now()

    if not last_refill:
        return -1  # Sentinel value indicating no refill event recorded

    time_until_next_refill = last_refill + refill_interval - now
    return max(0, int(time_until_next_refill.total_seconds()))


@router.post("/dewars/return", response_model=DewarSchema)
async def return_to_storage(data: LogisticsEventCreate, db: Session = Depends(get_db)):
    logger.info(
        f"Returning dewar to storage: {data.dewar_qr_code}"
        f"at location {data.location_qr_code}"
    )

    try:
        # Log the incoming payload
        logger.info("Received payload: %s", data.json())

        dewar = (
            db.query(DewarModel)
            .filter(DewarModel.unique_id == data.dewar_qr_code)
            .first()
        )
        if not dewar:
            logger.error(f"Dewar not found for unique ID: {data.dewar_qr_code}")
            raise HTTPException(status_code=404, detail="Dewar not found")

        original_slot = (
            db.query(SlotModel)
            .filter(SlotModel.dewar_unique_id == data.dewar_qr_code)
            .first()
        )
        if original_slot and original_slot.qr_code != data.location_qr_code:
            logger.error(
                f"Dewar {data.dewar_qr_code} is"
                f"associated with slot {original_slot.qr_code}"
            )
            raise HTTPException(
                status_code=400,
                detail=f"Dewar {data.dewar_qr_code} is associated"
                f"with a different slot {original_slot.qr_code}.",
            )

        slot = (
            db.query(SlotModel)
            .filter(SlotModel.qr_code == data.location_qr_code)
            .first()
        )
        if not slot:
            logger.error(f"Slot not found for QR code: {data.location_qr_code}")
            raise HTTPException(status_code=404, detail="Slot not found")

        if slot.occupied and slot.dewar_unique_id != data.dewar_qr_code:
            logger.error(
                f"Slot {data.location_qr_code} is already occupied by another dewar"
            )
            raise HTTPException(
                status_code=400,
                detail="Selected slot is already occupied by another dewar",
            )

        # Update slot with dewar information
        slot.dewar_unique_id = dewar.unique_id
        slot.occupied = True
        dewar.last_retrieved_timestamp = None

        # Set the `at_beamline` attribute to False
        dewar.at_beamline = False

        # Log the event
        log_event(db, dewar.id, slot.id, "returned")
        db.commit()

        logger.info(
            f"Dewar {data.dewar_qr_code} successfully"
            f"returned to storage slot {slot.qr_code}."
        )
        db.refresh(dewar)
        return dewar
    except ValidationError as e:
        logger.error(f"Validation error: {e.json()}")
        raise HTTPException(status_code=400, detail="Invalid payload")
    except Exception as e:
        logger.error(f"Unexpected error: {str(e)}")
        raise HTTPException(status_code=500, detail="Internal server error")


@router.post("/dewar/scan", response_model=dict)
async def scan_dewar(event_data: LogisticsEventCreate, db: Session = Depends(get_db)):
    logger.info(f"Received event data: {event_data}")

    dewar_qr_code = event_data.dewar_qr_code
    location_qr_code = event_data.location_qr_code
    transaction_type = event_data.transaction_type

    # Validate Dewar QR Code
    if not dewar_qr_code or not dewar_qr_code.strip():
        logger.error("Dewar QR Code is null or empty")
        raise HTTPException(
            status_code=422, detail="Dewar QR Code cannot be null or empty"
        )

    # Retrieve the Dewar
    dewar = db.query(DewarModel).filter(DewarModel.unique_id == dewar_qr_code).first()
    if not dewar:
        logger.error("Dewar not found")
        raise HTTPException(status_code=404, detail="Dewar not found")

    # Check for Outgoing QR Codes and set transaction type
    if location_qr_code in ["Outgoing X10-SA", "Outgoing X06-SA"]:
        transaction_type = "outgoing"

    # Retrieve the Slot associated with the Dewar (for outgoing)
    slot = None
    if transaction_type == "outgoing":
        slot = (
            db.query(SlotModel)
            .filter(SlotModel.dewar_unique_id == dewar.unique_id)
            .first()
        )
        if not slot:
            logger.error(f"No slot associated with dewar for outgoing: {dewar_qr_code}")
            raise HTTPException(
                status_code=404, detail="No slot associated with dewar for outgoing"
            )

    # Incoming Logic
    if transaction_type == "incoming":
        slot = db.query(SlotModel).filter(SlotModel.qr_code == location_qr_code).first()
        if not slot or slot.occupied:
            logger.error(f"Slot not found or already occupied: {location_qr_code}")
            raise HTTPException(
                status_code=400, detail="Slot not found or already occupied"
            )
        slot.dewar_unique_id = dewar.unique_id
        slot.occupied = True
    elif transaction_type == "outgoing":
        if not slot.occupied or slot.dewar_unique_id != dewar.unique_id:
            logger.error(f"Slot not valid for outgoing: {location_qr_code}")
            raise HTTPException(
                status_code=400,
                detail="Dewar not associated with the slot for outgoing",
            )
        slot.dewar_unique_id = None
        slot.occupied = False
    elif transaction_type == "beamline":
        slot = db.query(SlotModel).filter(SlotModel.qr_code == location_qr_code).first()
        if not slot:
            logger.error(f"Beamline location not found: {location_qr_code}")
            raise HTTPException(status_code=400, detail="Beamline location not found")
        dewar.beamline_location = location_qr_code
        logger.info(f"Dewar {dewar_qr_code} assigned to beamline {location_qr_code}")

    # Log the event
    log_event(db, dewar.id, slot.id if slot else None, transaction_type)
    db.commit()
    logger.info(
        f"Transaction completed: {transaction_type}"
        f"for dewar {dewar_qr_code} in slot {slot.qr_code if slot else 'N/A'}"
    )

    return {"message": "Status updated successfully"}


@router.get("/slots", response_model=List[SlotSchema])
async def get_all_slots(db: Session = Depends(get_db)):
    slots = db.query(SlotModel).options(joinedload(SlotModel.dewar)).all()
    slots_with_refill_time = []

    for slot in slots:
        # Initialize variables for slot-related data
        time_until_refill = None
        retrievedTimestamp = None
        beamlineLocation = None
        at_beamline = False

        if slot.dewar_unique_id:
            # Calculate time until refill
            last_refill_event = (
                db.query(LogisticsEventModel)
                .join(DewarModel, DewarModel.id == LogisticsEventModel.dewar_id)
                .filter(
                    DewarModel.unique_id == slot.dewar.unique_id,
                    LogisticsEventModel.event_type == "refill",
                )
                .order_by(LogisticsEventModel.timestamp.desc())
                .first()
            )

            if last_refill_event:
                last_refill = last_refill_event.timestamp
                time_until_refill = calculate_time_until_refill(last_refill)
            else:
                time_until_refill = -1

            # Fetch the latest event for the dewar
            last_event = (
                db.query(LogisticsEventModel)
                .join(DewarModel, DewarModel.id == LogisticsEventModel.dewar_id)
                .filter(DewarModel.unique_id == slot.dewar.unique_id)
                .order_by(LogisticsEventModel.timestamp.desc())
                .first()
            )

            # Determine if the dewar is at the beamline
            if last_event:
                if last_event.event_type == "beamline":
                    at_beamline = True
                    # Optionally set retrievedTimestamp and beamlineLocation for
                    # beamline events
                    retrievedTimestamp = last_event.timestamp.isoformat()
                    associated_slot = (
                        db.query(SlotModel)
                        .filter(SlotModel.id == last_event.slot_id)
                        .first()
                    )
                    beamlineLocation = (
                        associated_slot.label if associated_slot else None
                    )
                elif last_event.event_type == "returned":
                    at_beamline = False

        # Correct the contact_person assignment
        contact_person = None
        if slot.dewar and slot.dewar.contact_person:
            first_name = slot.dewar.contact_person.firstname
            last_name = slot.dewar.contact_person.lastname
            contact_person = f"{first_name} {last_name}"

        # Prepare the slot data for the response
        slot_data = SlotSchema(
            id=slot.id,
            qr_code=slot.qr_code,
            label=slot.label,
            qr_base=slot.qr_base,
            occupied=slot.occupied,
            needs_refill=slot.needs_refill,
            dewar_unique_id=slot.dewar_unique_id,
            dewar_name=slot.dewar.dewar_name if slot.dewar else None,
            time_until_refill=time_until_refill,
            at_beamline=at_beamline,
            retrievedTimestamp=retrievedTimestamp,
            beamlineLocation=beamlineLocation,
            shipment_name=(
                slot.dewar.shipment.shipment_name
                if slot.dewar and slot.dewar.shipment
                else None
            ),
            contact_person=contact_person,
            local_contact="local contact placeholder",
        )
        # Add updated slot data to the response list
        slots_with_refill_time.append(slot_data)

    return slots_with_refill_time


@router.post("/dewar/refill", response_model=dict)
async def refill_dewar(qr_code: str, db: Session = Depends(get_db)):
    logger.info(f"Refilling dewar with QR code: {qr_code}")

    dewar = db.query(DewarModel).filter(DewarModel.unique_id == qr_code.strip()).first()
    if not dewar:
        logger.error("Dewar not found")
        raise HTTPException(status_code=404, detail="Dewar not found")

    now = datetime.now()

    new_event = LogisticsEventModel(
        dewar_id=dewar.id,
        slot_id=None,
        event_type="refill",
        timestamp=now,
    )
    db.add(new_event)
    db.commit()

    time_until_refill_seconds = calculate_time_until_refill(now)
    logger.info(
        f"Dewar refilled successfully"
        f"with time_until_refill: {time_until_refill_seconds}"
    )

    return {
        "message": "Dewar refilled successfully",
        "time_until_refill": time_until_refill_seconds,
    }


@router.get("/dewars", response_model=List[DewarSchema])
async def get_all_dewars(db: Session = Depends(get_db)):
    dewars = db.query(DewarModel).all()
    return dewars


@router.get("/dewar/{unique_id}", response_model=DewarSchema)
async def get_dewar_by_unique_id(unique_id: str, db: Session = Depends(get_db)):
    logger.info(f"Received request for dewar with unique_id: {unique_id}")
    dewar = (
        db.query(DewarModel).filter(DewarModel.unique_id == unique_id.strip()).first()
    )
    if not dewar:
        logger.warning(f"Dewar with unique_id '{unique_id}' not found.")
        raise HTTPException(status_code=404, detail="Dewar not found")
    logger.info(f"Returning dewar: {dewar}")
    return dewar


def log_event(db: Session, dewar_id: int, slot_id: Optional[int], event_type: str):
    new_event = LogisticsEventModel(
        dewar_id=dewar_id,
        slot_id=slot_id,
        event_type=event_type,
        timestamp=datetime.now(),
    )
    db.add(new_event)
    db.commit()
    logger.info(
        f"Logged event: {event_type} for dewar: {dewar_id} "
        f"in slot: {slot_id if slot_id else 'N/A'}"
    )
