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

This module handles all database operations for clients, 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.client import Client
from api.schemas.client import ClientCreate, ClientUpdate


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

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

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

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

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

    return clients, total


def get_client_by_id(db: Session, client_id: UUID) -> Client:
    """
    Retrieve a single client by its ID.

    Args:
        db: Database session
        client_id: UUID of the client to retrieve

    Returns:
        Client: The client object

    Raises:
        HTTPException: 404 if client not found

    Example:
        ```python
        client = get_client_by_id(db, client_id)
        print(f"Found client: {client.name}")
        ```
    """
    client = db.query(Client).filter(Client.id == str(client_id)).first()

    if not client:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Client with ID {client_id} not found"
        )

    return client


def get_client_by_name(db: Session, name: str) -> Optional[Client]:
    """
    Retrieve a client by its name.

    Args:
        db: Database session
        name: Client name to search for

    Returns:
        Optional[Client]: The client if found, None otherwise

    Example:
        ```python
        client = get_client_by_name(db, "Acme Corporation")
        if client:
            print(f"Found client: {client.type}")
        ```
    """
    return db.query(Client).filter(Client.name == name).first()


def create_client(db: Session, client_data: ClientCreate) -> Client:
    """
    Create a new client.

    Args:
        db: Database session
        client_data: Client creation data

    Returns:
        Client: The created client object

    Raises:
        HTTPException: 409 if client with name already exists
        HTTPException: 500 if database error occurs

    Example:
        ```python
        client_data = ClientCreate(
            name="Acme Corporation",
            type="msp_client",
            primary_contact="John Doe"
        )
        client = create_client(db, client_data)
        print(f"Created client: {client.id}")
        ```
    """
    # Check if client with name already exists
    existing_client = get_client_by_name(db, client_data.name)
    if existing_client:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"Client with name '{client_data.name}' already exists"
        )

    try:
        # Create new client instance
        db_client = Client(**client_data.model_dump())

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

        return db_client

    except IntegrityError as e:
        db.rollback()
        # Handle unique constraint violations
        if "name" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"Client with name '{client_data.name}' 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 client: {str(e)}"
        )


def update_client(db: Session, client_id: UUID, client_data: ClientUpdate) -> Client:
    """
    Update an existing client.

    Args:
        db: Database session
        client_id: UUID of the client to update
        client_data: Client update data (only provided fields will be updated)

    Returns:
        Client: The updated client object

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

    Example:
        ```python
        update_data = ClientUpdate(
            primary_contact="Jane Smith",
            is_active=False
        )
        client = update_client(db, client_id, update_data)
        print(f"Updated client: {client.name}")
        ```
    """
    # Get existing client
    client = get_client_by_id(db, client_id)

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

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

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

        db.commit()
        db.refresh(client)

        return client

    except HTTPException:
        db.rollback()
        raise
    except IntegrityError as e:
        db.rollback()
        if "name" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail="Client with this name 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 client: {str(e)}"
        )


def delete_client(db: Session, client_id: UUID) -> dict:
    """
    Delete a client by its ID.

    Args:
        db: Database session
        client_id: UUID of the client to delete

    Returns:
        dict: Success message

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

    Example:
        ```python
        result = delete_client(db, client_id)
        print(result["message"])  # "Client deleted successfully"
        ```
    """
    # Get existing client (raises 404 if not found)
    client = get_client_by_id(db, client_id)

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

        return {
            "message": "Client deleted successfully",
            "client_id": str(client_id)
        }

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