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

This module handles all database operations for networks, 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.network import Network
from api.models.site import Site
from api.schemas.network import NetworkCreate, NetworkUpdate


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

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

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

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

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

    return networks, total


def get_network_by_id(db: Session, network_id: UUID) -> Network:
    """
    Retrieve a single network by its ID.

    Args:
        db: Database session
        network_id: UUID of the network to retrieve

    Returns:
        Network: The network object

    Raises:
        HTTPException: 404 if network not found

    Example:
        ```python
        network = get_network_by_id(db, network_id)
        print(f"Found network: {network.network_name}")
        ```
    """
    network = db.query(Network).filter(Network.id == str(network_id)).first()

    if not network:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Network with ID {network_id} not found"
        )

    return network


def get_networks_by_site(db: Session, site_id: UUID, skip: int = 0, limit: int = 100) -> tuple[list[Network], int]:
    """
    Retrieve networks belonging to a specific site.

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

    Returns:
        tuple: (list of networks, total count for this site)

    Raises:
        HTTPException: 404 if site not found

    Example:
        ```python
        networks, total = get_networks_by_site(db, site_id, skip=0, limit=50)
        print(f"Retrieved {len(networks)} of {total} networks for site")
        ```
    """
    # Verify site exists
    site = db.query(Site).filter(Site.id == str(site_id)).first()
    if not site:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Site with ID {site_id} not found"
        )

    # Get total count for this site
    total = db.query(Network).filter(Network.site_id == str(site_id)).count()

    # Get paginated results
    networks = (
        db.query(Network)
        .filter(Network.site_id == str(site_id))
        .order_by(Network.created_at.desc())
        .offset(skip)
        .limit(limit)
        .all()
    )

    return networks, total


def get_networks_by_type(db: Session, network_type: str, skip: int = 0, limit: int = 100) -> tuple[list[Network], int]:
    """
    Retrieve networks of a specific type.

    Args:
        db: Database session
        network_type: Type of network (lan, vpn, vlan, isolated, dmz)
        skip: Number of records to skip (for pagination)
        limit: Maximum number of records to return

    Returns:
        tuple: (list of networks, total count for this type)

    Example:
        ```python
        networks, total = get_networks_by_type(db, "vlan", skip=0, limit=50)
        print(f"Retrieved {len(networks)} of {total} VLAN networks")
        ```
    """
    # Get total count for this type
    total = db.query(Network).filter(Network.network_type == network_type).count()

    # Get paginated results
    networks = (
        db.query(Network)
        .filter(Network.network_type == network_type)
        .order_by(Network.created_at.desc())
        .offset(skip)
        .limit(limit)
        .all()
    )

    return networks, total


def create_network(db: Session, network_data: NetworkCreate) -> Network:
    """
    Create a new network.

    Args:
        db: Database session
        network_data: Network creation data

    Returns:
        Network: The created network object

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

    Example:
        ```python
        network_data = NetworkCreate(
            site_id="123e4567-e89b-12d3-a456-426614174000",
            network_name="Main LAN",
            network_type="lan",
            cidr="192.168.1.0/24"
        )
        network = create_network(db, network_data)
        print(f"Created network: {network.id}")
        ```
    """
    # Verify site exists if provided
    if network_data.site_id:
        site = db.query(Site).filter(Site.id == str(network_data.site_id)).first()
        if not site:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Site with ID {network_data.site_id} not found"
            )

    try:
        # Create new network instance
        db_network = Network(**network_data.model_dump())

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

        return db_network

    except IntegrityError as e:
        db.rollback()
        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 network: {str(e)}"
        )


def update_network(db: Session, network_id: UUID, network_data: NetworkUpdate) -> Network:
    """
    Update an existing network.

    Args:
        db: Database session
        network_id: UUID of the network to update
        network_data: Network update data (only provided fields will be updated)

    Returns:
        Network: The updated network object

    Raises:
        HTTPException: 404 if network or site not found
        HTTPException: 500 if database error occurs

    Example:
        ```python
        update_data = NetworkUpdate(
            network_name="Main LAN - Upgraded",
            gateway_ip="192.168.1.1"
        )
        network = update_network(db, network_id, update_data)
        print(f"Updated network: {network.network_name}")
        ```
    """
    # Get existing network
    network = get_network_by_id(db, network_id)

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

        # If updating site_id, verify new site exists
        if "site_id" in update_data and update_data["site_id"] is not None:
            site = db.query(Site).filter(Site.id == str(update_data["site_id"])).first()
            if not site:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail=f"Site with ID {update_data['site_id']} not found"
                )

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

        db.commit()
        db.refresh(network)

        return network

    except HTTPException:
        db.rollback()
        raise
    except IntegrityError as e:
        db.rollback()
        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 network: {str(e)}"
        )


def delete_network(db: Session, network_id: UUID) -> dict:
    """
    Delete a network by its ID.

    Args:
        db: Database session
        network_id: UUID of the network to delete

    Returns:
        dict: Success message

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

    Example:
        ```python
        result = delete_network(db, network_id)
        print(result["message"])  # "Network deleted successfully"
        ```
    """
    # Get existing network (raises 404 if not found)
    network = get_network_by_id(db, network_id)

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

        return {
            "message": "Network deleted successfully",
            "network_id": str(network_id)
        }

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