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

This module handles all database operations for tags, 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.tag import Tag
from api.schemas.tag import TagCreate, TagUpdate


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

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

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

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

    # Get paginated results, ordered by name ascending
    tags = (
        db.query(Tag)
        .order_by(Tag.name.asc())
        .offset(skip)
        .limit(limit)
        .all()
    )

    return tags, total


def get_tag_by_id(db: Session, tag_id: UUID) -> Tag:
    """
    Retrieve a single tag by its ID.

    Args:
        db: Database session
        tag_id: UUID of the tag to retrieve

    Returns:
        Tag: The tag object

    Raises:
        HTTPException: 404 if tag not found

    Example:
        ```python
        tag = get_tag_by_id(db, tag_id)
        print(f"Found tag: {tag.name}")
        ```
    """
    tag = db.query(Tag).filter(Tag.id == str(tag_id)).first()

    if not tag:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Tag with ID {tag_id} not found"
        )

    return tag


def get_tag_by_name(db: Session, name: str) -> Optional[Tag]:
    """
    Retrieve a tag by its name.

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

    Returns:
        Optional[Tag]: The tag if found, None otherwise

    Example:
        ```python
        tag = get_tag_by_name(db, "Windows")
        if tag:
            print(f"Found tag: {tag.description}")
        ```
    """
    return db.query(Tag).filter(Tag.name == name).first()


def get_tags_by_category(db: Session, category: str, skip: int = 0, limit: int = 100) -> tuple[list[Tag], int]:
    """
    Retrieve a paginated list of tags by category.

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

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

    Example:
        ```python
        tags, total = get_tags_by_category(db, "technology", skip=0, limit=50)
        print(f"Retrieved {len(tags)} of {total} technology tags")
        ```
    """
    # Get total count for category
    total = db.query(Tag).filter(Tag.category == category).count()

    # Get paginated results
    tags = (
        db.query(Tag)
        .filter(Tag.category == category)
        .order_by(Tag.name.asc())
        .offset(skip)
        .limit(limit)
        .all()
    )

    return tags, total


def create_tag(db: Session, tag_data: TagCreate) -> Tag:
    """
    Create a new tag.

    Args:
        db: Database session
        tag_data: Tag creation data

    Returns:
        Tag: The created tag object

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

    Example:
        ```python
        tag_data = TagCreate(
            name="Windows",
            category="technology",
            description="Microsoft Windows operating system"
        )
        tag = create_tag(db, tag_data)
        print(f"Created tag: {tag.id}")
        ```
    """
    # Check if tag with name already exists
    existing_tag = get_tag_by_name(db, tag_data.name)
    if existing_tag:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"Tag with name '{tag_data.name}' already exists"
        )

    try:
        # Create new tag instance
        db_tag = Tag(**tag_data.model_dump())

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

        return db_tag

    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"Tag with name '{tag_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 tag: {str(e)}"
        )


def update_tag(db: Session, tag_id: UUID, tag_data: TagUpdate) -> Tag:
    """
    Update an existing tag.

    Args:
        db: Database session
        tag_id: UUID of the tag to update
        tag_data: Tag update data (only provided fields will be updated)

    Returns:
        Tag: The updated tag object

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

    Example:
        ```python
        update_data = TagUpdate(
            description="Updated description",
            category="infrastructure"
        )
        tag = update_tag(db, tag_id, update_data)
        print(f"Updated tag: {tag.name}")
        ```
    """
    # Get existing tag
    tag = get_tag_by_id(db, tag_id)

    try:
        # Update only provided fields
        update_data = tag_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"] != tag.name:
            existing = get_tag_by_name(db, update_data["name"])
            if existing:
                raise HTTPException(
                    status_code=status.HTTP_409_CONFLICT,
                    detail=f"Tag with name '{update_data['name']}' already exists"
                )

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

        db.commit()
        db.refresh(tag)

        return tag

    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="Tag 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 tag: {str(e)}"
        )


def delete_tag(db: Session, tag_id: UUID) -> dict:
    """
    Delete a tag by its ID.

    Args:
        db: Database session
        tag_id: UUID of the tag to delete

    Returns:
        dict: Success message

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

    Example:
        ```python
        result = delete_tag(db, tag_id)
        print(result["message"])  # "Tag deleted successfully"
        ```
    """
    # Get existing tag (raises 404 if not found)
    tag = get_tag_by_id(db, tag_id)

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

        return {
            "message": "Tag deleted successfully",
            "tag_id": str(tag_id)
        }

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