"""
Machine service layer for business logic and database operations.

This module handles all database operations for machines, providing a clean
separation between the API routes and data access layer.
"""

from typing import Optional
from uuid import UUID

from fastapi import HTTPException, status
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session

from api.models.machine import Machine
from api.schemas.machine import MachineCreate, MachineUpdate


def get_machines(db: Session, skip: int = 0, limit: int = 100) -> tuple[list[Machine], int]:
    """
    Retrieve a paginated list of machines.

    Args:
        db: Database session
        skip: Number of records to skip (for pagination)
        limit: Maximum number of records to return

    Returns:
        tuple: (list of machines, total count)

    Example:
        ```python
        machines, total = get_machines(db, skip=0, limit=50)
        print(f"Retrieved {len(machines)} of {total} machines")
        ```
    """
    # Get total count
    total = db.query(Machine).count()

    # Get paginated results, ordered by created_at descending (newest first)
    machines = (
        db.query(Machine)
        .order_by(Machine.created_at.desc())
        .offset(skip)
        .limit(limit)
        .all()
    )

    return machines, total


def get_machine_by_id(db: Session, machine_id: UUID) -> Machine:
    """
    Retrieve a single machine by its ID.

    Args:
        db: Database session
        machine_id: UUID of the machine to retrieve

    Returns:
        Machine: The machine object

    Raises:
        HTTPException: 404 if machine not found

    Example:
        ```python
        machine = get_machine_by_id(db, machine_id)
        print(f"Found machine: {machine.hostname}")
        ```
    """
    machine = db.query(Machine).filter(Machine.id == str(machine_id)).first()

    if not machine:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Machine with ID {machine_id} not found"
        )

    return machine


def get_machine_by_hostname(db: Session, hostname: str) -> Optional[Machine]:
    """
    Retrieve a machine by its hostname.

    Args:
        db: Database session
        hostname: Hostname to search for

    Returns:
        Optional[Machine]: The machine if found, None otherwise

    Example:
        ```python
        machine = get_machine_by_hostname(db, "laptop-dev-01")
        if machine:
            print(f"Found machine: {machine.friendly_name}")
        ```
    """
    return db.query(Machine).filter(Machine.hostname == hostname).first()


def create_machine(db: Session, machine_data: MachineCreate) -> Machine:
    """
    Create a new machine.

    Args:
        db: Database session
        machine_data: Machine creation data

    Returns:
        Machine: The created machine object

    Raises:
        HTTPException: 409 if machine with hostname already exists
        HTTPException: 500 if database error occurs

    Example:
        ```python
        machine_data = MachineCreate(
            hostname="laptop-dev-01",
            friendly_name="Development Laptop",
            platform="win32"
        )
        machine = create_machine(db, machine_data)
        print(f"Created machine: {machine.id}")
        ```
    """
    # Check if machine with hostname already exists
    existing_machine = get_machine_by_hostname(db, machine_data.hostname)
    if existing_machine:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"Machine with hostname '{machine_data.hostname}' already exists"
        )

    try:
        # Create new machine instance
        db_machine = Machine(**machine_data.model_dump())

        # Add to database
        db.add(db_machine)
        db.commit()
        db.refresh(db_machine)

        return db_machine

    except IntegrityError as e:
        db.rollback()
        # Handle unique constraint violations
        if "hostname" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"Machine with hostname '{machine_data.hostname}' already exists"
            )
        elif "machine_fingerprint" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail="Machine with this fingerprint already exists"
            )
        else:
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail=f"Database error: {str(e)}"
            )
    except Exception as e:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to create machine: {str(e)}"
        )


def update_machine(db: Session, machine_id: UUID, machine_data: MachineUpdate) -> Machine:
    """
    Update an existing machine.

    Args:
        db: Database session
        machine_id: UUID of the machine to update
        machine_data: Machine update data (only provided fields will be updated)

    Returns:
        Machine: The updated machine object

    Raises:
        HTTPException: 404 if machine not found
        HTTPException: 409 if update would violate unique constraints
        HTTPException: 500 if database error occurs

    Example:
        ```python
        update_data = MachineUpdate(
            friendly_name="Updated Laptop Name",
            is_active=False
        )
        machine = update_machine(db, machine_id, update_data)
        print(f"Updated machine: {machine.friendly_name}")
        ```
    """
    # Get existing machine
    machine = get_machine_by_id(db, machine_id)

    try:
        # Update only provided fields
        update_data = machine_data.model_dump(exclude_unset=True)

        # If updating hostname, check if new hostname is already taken
        if "hostname" in update_data and update_data["hostname"] != machine.hostname:
            existing = get_machine_by_hostname(db, update_data["hostname"])
            if existing:
                raise HTTPException(
                    status_code=status.HTTP_409_CONFLICT,
                    detail=f"Machine with hostname '{update_data['hostname']}' already exists"
                )

        # Apply updates
        for field, value in update_data.items():
            setattr(machine, field, value)

        db.commit()
        db.refresh(machine)

        return machine

    except HTTPException:
        db.rollback()
        raise
    except IntegrityError as e:
        db.rollback()
        if "hostname" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail="Machine with this hostname already exists"
            )
        elif "machine_fingerprint" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail="Machine with this fingerprint already exists"
            )
        else:
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail=f"Database error: {str(e)}"
            )
    except Exception as e:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to update machine: {str(e)}"
        )


def delete_machine(db: Session, machine_id: UUID) -> dict:
    """
    Delete a machine by its ID.

    Args:
        db: Database session
        machine_id: UUID of the machine to delete

    Returns:
        dict: Success message

    Raises:
        HTTPException: 404 if machine not found
        HTTPException: 500 if database error occurs

    Example:
        ```python
        result = delete_machine(db, machine_id)
        print(result["message"])  # "Machine deleted successfully"
        ```
    """
    # Get existing machine (raises 404 if not found)
    machine = get_machine_by_id(db, machine_id)

    try:
        db.delete(machine)
        db.commit()

        return {
            "message": "Machine deleted successfully",
            "machine_id": str(machine_id)
        }

    except Exception as e:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to delete machine: {str(e)}"
        )


def get_active_machines(db: Session, skip: int = 0, limit: int = 100) -> tuple[list[Machine], int]:
    """
    Retrieve a paginated list of active machines only.

    Args:
        db: Database session
        skip: Number of records to skip (for pagination)
        limit: Maximum number of records to return

    Returns:
        tuple: (list of active machines, total count)

    Example:
        ```python
        machines, total = get_active_machines(db, skip=0, limit=50)
        print(f"Retrieved {len(machines)} of {total} active machines")
        ```
    """
    # Get total count of active machines
    total = db.query(Machine).filter(Machine.is_active == True).count()

    # Get paginated results
    machines = (
        db.query(Machine)
        .filter(Machine.is_active == True)
        .order_by(Machine.created_at.desc())
        .offset(skip)
        .limit(limit)
        .all()
    )

    return machines, total


def get_primary_machine(db: Session) -> Optional[Machine]:
    """
    Retrieve the primary machine.

    Args:
        db: Database session

    Returns:
        Optional[Machine]: The primary machine if one exists, None otherwise

    Example:
        ```python
        primary = get_primary_machine(db)
        if primary:
            print(f"Primary machine: {primary.hostname}")
        ```
    """
    return db.query(Machine).filter(Machine.is_primary == True).first()
