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

This module handles all database operations for projects, 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.project import Project
from api.models.client import Client
from api.schemas.project import ProjectCreate, ProjectUpdate


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

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

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

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

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

    return projects, total


def get_project_by_id(db: Session, project_id: UUID) -> Project:
    """
    Retrieve a single project by its ID.

    Args:
        db: Database session
        project_id: UUID of the project to retrieve

    Returns:
        Project: The project object

    Raises:
        HTTPException: 404 if project not found

    Example:
        ```python
        project = get_project_by_id(db, project_id)
        print(f"Found project: {project.name}")
        ```
    """
    project = db.query(Project).filter(Project.id == str(project_id)).first()

    if not project:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Project with ID {project_id} not found"
        )

    return project


def get_project_by_slug(db: Session, slug: str) -> Optional[Project]:
    """
    Retrieve a project by its slug.

    Args:
        db: Database session
        slug: Slug to search for

    Returns:
        Optional[Project]: The project if found, None otherwise

    Example:
        ```python
        project = get_project_by_slug(db, "dataforth-dos")
        if project:
            print(f"Found project: {project.name}")
        ```
    """
    return db.query(Project).filter(Project.slug == slug).first()


def get_projects_by_client(db: Session, client_id: str, skip: int = 0, limit: int = 100) -> tuple[list[Project], int]:
    """
    Retrieve projects for a specific client.

    Args:
        db: Database session
        client_id: Client UUID
        skip: Number of records to skip
        limit: Maximum number of records to return

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

    Example:
        ```python
        projects, total = get_projects_by_client(db, client_id)
        print(f"Client has {total} projects")
        ```
    """
    total = db.query(Project).filter(Project.client_id == str(client_id)).count()

    projects = (
        db.query(Project)
        .filter(Project.client_id == str(client_id))
        .order_by(Project.created_at.desc())
        .offset(skip)
        .limit(limit)
        .all()
    )

    return projects, total


def get_projects_by_status(db: Session, status_filter: str, skip: int = 0, limit: int = 100) -> tuple[list[Project], int]:
    """
    Retrieve projects by status.

    Args:
        db: Database session
        status_filter: Status to filter by (complete, working, blocked, pending, critical, deferred)
        skip: Number of records to skip
        limit: Maximum number of records to return

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

    Example:
        ```python
        projects, total = get_projects_by_status(db, "working")
        print(f"Found {total} working projects")
        ```
    """
    total = db.query(Project).filter(Project.status == status_filter).count()

    projects = (
        db.query(Project)
        .filter(Project.status == status_filter)
        .order_by(Project.created_at.desc())
        .offset(skip)
        .limit(limit)
        .all()
    )

    return projects, total


def validate_client_exists(db: Session, client_id: str) -> None:
    """
    Validate that a client exists.

    Args:
        db: Database session
        client_id: Client UUID to validate

    Raises:
        HTTPException: 404 if client not found

    Example:
        ```python
        validate_client_exists(db, client_id)
        # Continues if client exists, raises HTTPException if not
        ```
    """
    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"
        )


def create_project(db: Session, project_data: ProjectCreate) -> Project:
    """
    Create a new project.

    Args:
        db: Database session
        project_data: Project creation data

    Returns:
        Project: The created project object

    Raises:
        HTTPException: 404 if client not found
        HTTPException: 409 if project with slug already exists
        HTTPException: 500 if database error occurs

    Example:
        ```python
        project_data = ProjectCreate(
            client_id="123e4567-e89b-12d3-a456-426614174000",
            name="Client Website Redesign",
            status="working"
        )
        project = create_project(db, project_data)
        print(f"Created project: {project.id}")
        ```
    """
    # Validate client exists
    validate_client_exists(db, project_data.client_id)

    # Check if project with slug already exists (if slug is provided)
    if project_data.slug:
        existing_project = get_project_by_slug(db, project_data.slug)
        if existing_project:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"Project with slug '{project_data.slug}' already exists"
            )

    try:
        # Create new project instance
        db_project = Project(**project_data.model_dump())

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

        return db_project

    except IntegrityError as e:
        db.rollback()
        # Handle unique constraint violations
        if "slug" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"Project with slug '{project_data.slug}' already exists"
            )
        elif "client_id" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Client with ID {project_data.client_id} not found"
            )
        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 project: {str(e)}"
        )


def update_project(db: Session, project_id: UUID, project_data: ProjectUpdate) -> Project:
    """
    Update an existing project.

    Args:
        db: Database session
        project_id: UUID of the project to update
        project_data: Project update data (only provided fields will be updated)

    Returns:
        Project: The updated project object

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

    Example:
        ```python
        update_data = ProjectUpdate(
            status="completed",
            completed_date=date.today()
        )
        project = update_project(db, project_id, update_data)
        print(f"Updated project: {project.name}")
        ```
    """
    # Get existing project
    project = get_project_by_id(db, project_id)

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

        # If updating client_id, validate client exists
        if "client_id" in update_data and update_data["client_id"] != project.client_id:
            validate_client_exists(db, update_data["client_id"])

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

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

        db.commit()
        db.refresh(project)

        return project

    except HTTPException:
        db.rollback()
        raise
    except IntegrityError as e:
        db.rollback()
        if "slug" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail="Project with this slug already exists"
            )
        elif "client_id" in str(e.orig):
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Client not found"
            )
        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 project: {str(e)}"
        )


def delete_project(db: Session, project_id: UUID) -> dict:
    """
    Delete a project by its ID.

    Args:
        db: Database session
        project_id: UUID of the project to delete

    Returns:
        dict: Success message

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

    Example:
        ```python
        result = delete_project(db, project_id)
        print(result["message"])  # "Project deleted successfully"
        ```
    """
    # Get existing project (raises 404 if not found)
    project = get_project_by_id(db, project_id)

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

        return {
            "message": "Project deleted successfully",
            "project_id": str(project_id)
        }

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