"""
File services module for handling file operations.

This module provides services for file upload, download, listing, and deletion
operations with support for both local filesystem and CDN storage.
"""

import logging
import os
from typing import Dict, List, Optional, Tuple, Any
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from sqlalchemy import select
from starlette import status
from starlette.responses import Response
from fastapi.responses import StreamingResponse

from src.apps.base.services import get_active_cdn
from src.apps.files.helper.io import (
    get_approximate_filesize,
    remove_file_single,
    upload_file_multiple,
    upload_file_single,
    download_file_from_s3,
    download_file_from_system_path,
    get_image_resolution,
)
from src.apps.files.models.file import File
from src.apps.files.schemas.file_common import FileSchema, FileResponseSchema
from src.core.exceptions import APIException
from src.core.utils.enums import DBSortTypes, FileTypes
from src.core.utils.constants import (
    MAX_UPLOAD_LIMIT,
    MAX_UPLOAD_SIZE_MB,
    ALLOWED_FILE_CONTENT_TYPES,
    MAX_UPLOAD_RESOLUTION,
    THUMBNAIL_ALLOWED_FILE_CONTENT_TYPES,
    THUMBNAIL_MAX_FILE_SIZE_BYTES,
)
from src.apps.base.utils.regexp import (
    test,
    INVOICE_UNIQUE_ID,
    RECEIPT_UNIQUE_ID,
    CONTRACT_UNIQUE_ID,
)

logger = logging.getLogger(__name__)


async def upload_single_file(
    db: Session,
    file,
    file_type: str,
    created_by,
    merchant,
) -> FileResponseSchema:
    """
    Upload a single file and create database entry.
    
    Args:
        db: Database session.
        file: FastAPI UploadFile object.
        file_type: Type of file being uploaded.
        created_by: User who is uploading the file.
        merchant: Merchant context for file organization.
        
    Returns:
        FileResponseSchema with file details.
        
    Raises:
        APIException: If validation fails or upload errors occur.
    """
    _validate_file_type_and_content(file, file_type)
    
    file_content = file.file.read()
    _validate_file_content(file_content, file_type)
    
    if file_type == "thumbnail":
        _validate_thumbnail_requirements(file, file_content)
    
    uploaded_file_data = upload_file_single(
        db, file, file_content, f"{merchant.merchant_id}/"
    )
    
    new_file = await _create_file_database_entry(
        db=db,
        file_data=uploaded_file_data,
        file_type=file_type,
        created_by=created_by
    )
    
    return new_file


async def upload_multiple_files(
    db: Session,
    files: List,
    file_type: str,
    created_by,
    merchant,
) -> List[FileResponseSchema]:
    """
    Upload multiple files and create database entries.
    
    Args:
        db: Database session.
        files: List of FastAPI UploadFile objects.
        file_type: Type of files being uploaded.
        created_by: User who is uploading the files.
        merchant: Merchant context for file organization.
        
    Returns:
        List of FileResponseSchema with file details.
        
    Raises:
        APIException: If validation fails or upload errors occur.
    """
    if len(files) > MAX_UPLOAD_LIMIT:
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message=f"A maximum of {MAX_UPLOAD_LIMIT} files can be uploaded at once"
        )
    
    _validate_multiple_file_types(files)
    
    uploaded_files_data = upload_file_multiple(
        db, files, f"{merchant.merchant_id}/"
    )
    
    new_files = await _create_multiple_file_database_entries(
        db=db,
        files_data=uploaded_files_data,
        file_type=file_type,
        created_by=created_by
    )
    
    return new_files


async def get_file_by_id(db: Session, file_id: int) -> FileSchema:
    """
    Retrieve a file by its ID.
    
    Args:
        db: Database session.
        file_id: ID of the file to retrieve.
        created_by: User requesting the file.
        
    Returns:
        FileSchema with file details.
        
    Raises:
        APIException: If file not found.
    """
    stmt = select(File).where(
        File.id == file_id
    )
    result = db.execute(stmt)
    file_obj = result.scalar_one_or_none()
    
    if not file_obj:
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_404_NOT_FOUND,
            message="No such file found"
        )
    
    return FileSchema.model_validate(file_obj)


async def list_user_files(
    db: Session,
    ids: str,
    skip: int,
    limit: int,
    created_by
) -> List[FileSchema]:
    """
    List files for a user with optional filtering by IDs.
    
    Args:
        db: Database session.
        ids: Comma-separated string of file IDs to filter by.
        skip: Number of records to skip for pagination.
        limit: Maximum number of records to return.
        created_by: User requesting the files.
        
    Returns:
        List of FileSchema objects.
        
    Raises:
        APIException: If validation fails or files not found.
    """
    if ids:
        return await _get_files_by_ids(db, ids, created_by)
    else:
        return await _get_paginated_files(db, skip, limit, created_by)


async def delete_file_by_id(db: Session, file_id: int, created_by) -> Dict[str, bool]:
    """
    Delete a file by its ID.
    
    Args:
        db: Database session.
        file_id: ID of the file to delete.
        created_by: User requesting the deletion.
        
    Returns:
        Dictionary indicating successful deletion.
        
    Raises:
        APIException: If file not found or deletion fails.
    """
    # First get the file to access its path for physical deletion
    stmt = select(File).where(
        File.id == file_id,
        File.created_by_id == created_by.id
    )
    result = db.execute(stmt)
    file_obj = result.scalar_one_or_none()
    
    if not file_obj:
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_404_NOT_FOUND,
            message="No such file found"
        )
    
    try:
        # Delete from database
        db.delete(file_obj)
        db.commit()
        
        # Delete physical file
        remove_file_single(path=file_obj.upload_path)
        
        return {"deleted": True}
    except Exception as e:
        db.rollback()
        logger.error(f"Error deleting file {file_id}: {e}")
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            message="Failed to delete file"
        )


def download_file_by_path(
    db: Session,
    path: str,
    current_user=None
) -> Response:
    """
    Download a file by its path.
    
    Args:
        db: Database session.
        path: Path to the file.
        current_user: Currently authenticated user (optional).
        
    Returns:
        Response with file content.
        
    Raises:
        APIException: If file not found or access denied.
    """
    if not path:
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message="Path is required"
        )
    
    file_obj = _find_file_by_path(db, path, current_user)
    _validate_file_access(file_obj, current_user)
    
    file_content = _download_file_content(db, file_obj)
    
    return Response(content=file_content, media_type=file_obj.mime)


# Helper functions

def _validate_file_type_and_content(file, file_type: str) -> None:
    """Validate file type and basic content requirements."""
    content_type = file.content_type.split("/")[1]
    if content_type not in ALLOWED_FILE_CONTENT_TYPES:
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message=f"Cannot upload '{content_type}' files. Allowed content types are {', '.join(ALLOWED_FILE_CONTENT_TYPES)}"
        )


def _validate_file_content(file_content: bytes, file_type: str) -> None:
    """Validate file content size and basic requirements."""
    if len(file_content) == 0:
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message="The file you are trying to upload is empty"
        )
    
    if len(file_content) > (MAX_UPLOAD_SIZE_MB * 1000000):
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message=f"File exceeds maximum size limit of {MAX_UPLOAD_SIZE_MB}mb"
        )


def _validate_thumbnail_requirements(file, file_content: bytes) -> None:
    """Validate specific requirements for thumbnail files."""
    if file.content_type not in THUMBNAIL_ALLOWED_FILE_CONTENT_TYPES:
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message=f"Invalid file type '{file.content_type}'. Only JPG and PNG are allowed."
        )
    
    if len(file_content) > (THUMBNAIL_MAX_FILE_SIZE_BYTES * 1024):
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message=f"File exceeds maximum size limit of {THUMBNAIL_MAX_FILE_SIZE_BYTES}KB."
        )
    
    try:
        resolution = get_image_resolution(file_content)
        if resolution and (
            resolution[0] > MAX_UPLOAD_RESOLUTION[0] or 
            resolution[1] > MAX_UPLOAD_RESOLUTION[1]
        ):
            raise APIException(
                module=__name__,
                error={},
                status_code=status.HTTP_400_BAD_REQUEST,
                message=f"Image resolution exceeds the maximum allowed {MAX_UPLOAD_RESOLUTION[0]}x{MAX_UPLOAD_RESOLUTION[1]} pixels."
            )
    except ValueError:
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message="Failed to parse image resolution."
        )


def _validate_multiple_file_types(files: List) -> None:
    """Validate file types for multiple file upload."""
    for file in files:
        extension = file.content_type.split("/")[1]
        if extension not in ALLOWED_FILE_CONTENT_TYPES:
            raise APIException(
                module=__name__,
                error={},
                status_code=status.HTTP_400_BAD_REQUEST,
                message=f"Cannot upload {extension} files. Allowed file extensions are {', '.join(ALLOWED_FILE_CONTENT_TYPES)}"
            )


async def _create_file_database_entry(
    db: Session,
    file_data: Dict,
    file_type: str,
    created_by
) -> FileResponseSchema:
    """Create a single file entry in the database."""
    try:
        active_cdn = get_active_cdn(db=db)
        
        new_file = File(
            name=file_data["file_name"],
            original_name=file_data["original_name"] + f".{file_data['format']}",
            mime=file_data["content_type"],
            file_type=file_type,
            created_by_id=created_by.id,
            path=file_data["path"],
            cdn_id=active_cdn.id if active_cdn else None
        )
        
        db.add(new_file)
        db.commit()
        db.refresh(new_file)
        
        result = FileResponseSchema.model_validate(new_file)
        result.filesize = file_data["filesize"]
        
        return result
    except IntegrityError:
        db.rollback()
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message="Attempting to create duplicate file"
        )
    except Exception as e:
        db.rollback()
        logger.error(f"Error creating file entry: {e}")
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            message="Failed to create file entry"
        )


async def _create_multiple_file_database_entries(
    db: Session,
    files_data: List[Dict],
    file_type: str,
    created_by
) -> List[FileResponseSchema]:
    """Create multiple file entries in the database."""
    try:
        active_cdn = get_active_cdn(db=db)
        
        files_list = []
        for file_data in files_data:
            new_file = File(
                name=file_data["file_name"],
                original_name=file_data["original_name"] + f".{file_data['format']}",
                mime=file_data["content_type"],
                file_type=file_type,
                created_by_id=created_by.id,
                path=file_data["path"],
                cdn_id=active_cdn.id if active_cdn else None
            )
            files_list.append(new_file)
        
        db.add_all(files_list)
        db.commit()
        
        response_list = []
        for file_obj in files_list:
            db.refresh(file_obj)
            result = FileResponseSchema.model_validate(file_obj)
            result.filesize = get_approximate_filesize(
                f"{active_cdn.upload_path}{file_obj.path}", active_cdn
            )
            response_list.append(result)
        
        return response_list
    except IntegrityError:
        db.rollback()
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message="Attempting to create duplicate file"
        )
    except Exception as e:
        db.rollback()
        logger.error(f"Error creating multiple file entries: {e}")
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            message="Failed to create file entries"
        )


async def _get_files_by_ids(db: Session, ids: str, created_by) -> List[FileSchema]:
    """Get files filtered by specific IDs."""
    try:
        file_ids = [int(fid) for fid in ids.split(",")]
        if len(file_ids) > 10:
            raise APIException(
                module=__name__,
                error={},
                status_code=status.HTTP_400_BAD_REQUEST,
                message="A maximum of 10 files can be requested at once"
            )
        
        stmt = select(File).where(
            File.id.in_(file_ids),
            File.created_by_id == created_by.id
        )
        result = db.execute(stmt)
        files = result.scalars().all()
        
        return [FileSchema.model_validate(file_obj) for file_obj in files]
    except ValueError:
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_400_BAD_REQUEST,
            message="Invalid file ID format"
        )


async def _get_paginated_files(db: Session, skip: int, limit: int, created_by) -> List[FileSchema]:
    """Get paginated files for a user."""
    stmt = select(File).where(
        File.created_by_id == created_by.id
    ).order_by(File.created_at.desc()).offset(skip).limit(limit)
    
    result = db.execute(stmt)
    files = result.scalars().all()
    
    return [FileSchema.model_validate(file_obj) for file_obj in files]


def _find_file_by_path(db: Session, path: str, current_user) -> File:
    """Find file by path with proper access control."""
    active_cdn = get_active_cdn(db=db)
    
    # Sanitize the path for database query
    sanitized_path = path
    if active_cdn:
        for prefix in [active_cdn.host, active_cdn.root, active_cdn.path]:
            if prefix and prefix in sanitized_path:
                sanitized_path = sanitized_path.replace(prefix, "")
    
    # Check if it's a private file
    file_storage_key = sanitized_path.replace("/", "")
    is_private_file = any([
        test(file_storage_key, pattern)
        for pattern in [INVOICE_UNIQUE_ID, RECEIPT_UNIQUE_ID, CONTRACT_UNIQUE_ID]
    ])
    
    # Query the database
    if is_private_file:
        if not current_user:
            raise APIException(
                module=__name__,
                error={},
                status_code=status.HTTP_403_FORBIDDEN,
                message="Authentication required to access this resource"
            )
        stmt = select(File).where(File.storage_key == file_storage_key)
    else:
        stmt = select(File).where(File.path == sanitized_path)
    
    result = db.execute(stmt)
    file_obj = result.scalar_one_or_none()
    
    if not file_obj:
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_404_NOT_FOUND,
            message="No such file found"
        )
    
    return file_obj


def _validate_file_access(file_obj: File, current_user) -> None:
    """Validate user access to the file."""
    if file_obj.storage_key and not current_user:
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_403_FORBIDDEN,
            message="Authentication required to access this resource"
        )


def _download_file_content(db: Session, file_obj: File) -> bytes:
    """Download file content from storage."""
    active_cdn = get_active_cdn(db=db)
    
    if active_cdn and active_cdn.label.lower() == "s3":
        file_content = download_file_from_s3(
            path=f"{active_cdn.upload_path}{file_obj.path}"
        )
    else:
        file_content = download_file_from_system_path(
            path=f"{active_cdn.upload_path}{file_obj.path}"
        )
    
    if not file_content or len(file_content) < 1:
        raise APIException(
            module=__name__,
            error={},
            status_code=status.HTTP_404_NOT_FOUND,
            message="The resource you are trying to access is unavailable"
        )
    
    return file_content
