from datetime import datetime
from uuid import UUID
import os
import aiofiles
from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from fastapi.responses import FileResponse
from starlette.responses import StreamingResponse
from src.core.exceptions import APIException
from src.utils.pagination import QueryPaginator
from src.utils.constants import EXPORT_DATA_SUB_FOLDER
from sqlalchemy import func, cast, Integer,and_,or_
from src.apps.wine.global_noise.models.global_noise import GlobalNoise
from src.apps.wine.global_noise.schemas.global_noise import (
    GlobalNoiseCreateSchema,
    GlobalNoiseFilterSchema,
    GlobalNoiseOutputSchema,
    GlobalNoiseUpdateSchema
)
from src.utils.constants import API_PREFIXES
from src.core.config import settings
from typing import Dict, List
from sqlalchemy.sql.expression import nullslast
from src.core.exceptions import APIException


async def get_all_global_noise_service(
    db: Session, 
    payload: GlobalNoiseFilterSchema, 
    page: int = 1, 
    per_page: int = 10, 
    sort_by: List[str] = None
) -> Dict:
    """
    Get all global noise entries with pagination and filtering.
    """
    
    query, _ = await _build_global_noise_query(db, payload, sort_by)
    offset = (page - 1) * per_page

    paginator = QueryPaginator(
        query=query,
        schema=GlobalNoiseOutputSchema,
        url="".join([str(settings.api_base_url()), API_PREFIXES.GLOBAL_NOISE]),
        offset=offset,
        limit=per_page,
    )

    return paginator.paginate()

async def download_noise_service(
    db: Session, 
    payload: GlobalNoiseFilterSchema
) -> StreamingResponse:
    """
    Download global noise data as a text file with streaming response.
    """
    try:
        # Build query using the existing helper function
        query, _ = await _build_global_noise_query(db, payload, sort_by=["-updated_at"])
        
        # Get only the noise column
        noise_list = query.with_entities(GlobalNoise.noise).all()
        
        # Generate filename with timestamp
        current_time = datetime.now().strftime('%Y-%m-%d-%H%M%S')
        filename = f"global-noise_{current_time}.txt"
        
        # Create downloads directory if it doesn't exist
        download_dir = EXPORT_DATA_SUB_FOLDER
        os.makedirs(download_dir, exist_ok=True)
        
        # Full file path
        file_path = os.path.join(download_dir, filename)
        
        async def stream_generator():
            # Open the file and write while yielding to the client
            with open(file_path, "w", encoding="utf-8") as fh:
                for noise_tuple in noise_list:
                    if noise_tuple[0]:  # noise_tuple is a tuple, get the first element
                        line = f"{noise_tuple[0]}\n"
                        # Write to disk and yield to client
                        fh.write(line)
                        yield line
                
                # Flush to ensure complete write
                fh.flush()

        return StreamingResponse(
            stream_generator(),
            media_type="text/plain",
            headers={
                "Content-Disposition": f"attachment; filename={filename}",
                "Transfer-Encoding": "chunked"
            }
        )
        
    except Exception as e:
        raise APIException(
            module="download_noise_service",
            error=str(e),
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            message="Error downloading global noise data."
        )


async def _build_global_noise_query(
    db: Session, 
    payload: GlobalNoiseFilterSchema, 
    sort_by: List[str] = None
) -> tuple:
    """
    Build the query for global noises based on filters and sorting.
    """
    query = db.query(GlobalNoise).filter(GlobalNoise.deleted_at.is_(None))

    if hasattr(payload, 'search') and payload.search:
        search_term = f"%{payload.search.strip()}%"
        query = query.filter(
            GlobalNoise.noise.ilike(search_term)
        )

    if hasattr(payload, 'date_from') and payload.date_from:
        query = query.filter(GlobalNoise.created_at >= payload.date_from)

    if hasattr(payload, 'date_to') and payload.date_to:
        query = query.filter(GlobalNoise.created_at <= payload.date_to)

    # Apply sorting if provided
    sort_query = []
    if sort_by:
        for s in sort_by:
            try:
                col = s.replace("-", "")
                if col in ["id", "noise", "created_at", "updated_at"]:
                    sort_order = getattr(GlobalNoise, col)

                    if s.startswith("-"):
                        sort_order = sort_order.desc()

                    sort_query.append(nullslast(sort_order))
                else:
                    raise APIException(
                        module="get_all_global_noise_service",
                        error=f"Cannot sort with unidentified column '{s}'",
                        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                        message="Error retrieving global noises."
                    )
            except AttributeError:
                raise APIException(
                    module="get_all_global_noise_service",
                    error=f"Cannot sort with unidentified column '{s}'",
                    status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                    message="Error retrieving global noises."
                )

        if sort_query:
            query = query.order_by(*sort_query)

    return query, sort_query

async def create_global_noise(
    db: Session, 
    payload: GlobalNoiseCreateSchema
) -> GlobalNoiseOutputSchema:
    """
    Create a new global noise entry.
    """
    try:
        existing_noise = await check_existing_global_noise(db, payload.noise)
        if existing_noise:
            raise APIException(
                module="create_global_noise",
                error={"noise": "Noise already exists."},
                status_code=status.HTTP_400_BAD_REQUEST,
                message="Noise already exists."
            )
            
        new_noise = GlobalNoise(
            noise=payload.noise
        )
        db.add(new_noise)
        db.commit()
        db.refresh(new_noise)
        return GlobalNoiseOutputSchema.from_orm(new_noise)
    except Exception as e:
        raise APIException(
            module="create_global_noise",
            error=str(e),
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            message="Error creating global noise."
        )
        
async def get_global_noise_by_id_service(
    db: Session, 
    global_noise_id: int
) -> GlobalNoiseOutputSchema:
    """
    Retrieve a global noise entry by its ID.
    """
    try:
        noise = db.query(GlobalNoise).filter(
            GlobalNoise.id == global_noise_id,
            GlobalNoise.deleted_at.is_(None)
        ).first()
        
        if not noise:
            raise APIException(
                module="get_global_noise_by_id_service",
                error=f"Global noise with ID {global_noise_id} not found.",
                status_code=status.HTTP_404_NOT_FOUND,
                message="Global noise not found."
            )
        
        return noise
    except Exception as e:
        raise APIException(
            module="get_global_noise_by_id_service",
            error=str(e),
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            message="Error retrieving global noise."
        )
        
async def update_global_noise(
    db: Session, 
    global_noise_id: int, 
    payload: GlobalNoiseUpdateSchema
) -> GlobalNoiseOutputSchema:
    """
    Update an existing global noise entry.
    """
    try:
        update_noise = await get_global_noise_by_id_service(db, global_noise_id)
        if payload.noise and payload.noise != update_noise.noise:
            existing_noise = await check_existing_global_noise(db, payload.noise)
            if existing_noise:
                raise APIException(
                    module="update_global_noise",
                    error={"noise": "Noise already exists."},
                    status_code=status.HTTP_400_BAD_REQUEST,
                    message="Noise already exists."
                )
        if payload.noise is not None:
            update_noise.noise = payload.noise
        
        db.commit()
        db.refresh(update_noise)
        return GlobalNoiseOutputSchema.model_validate(update_noise)
    except Exception as e:
        raise APIException(
            module="update_global_noise",
            error=str(e),
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            message="Error updating global noise."
        )
        
        
async def delete_global_noise(
    db: Session, 
    global_noise_id: int
) -> any:
    """
    Soft delete a global noise entry by setting its deleted_at timestamp.
    """
    try:
        noise = await get_global_noise_by_id_service(db, global_noise_id)
        
        if not noise:
            raise APIException(
                module="delete_global_noise",
                error=f"Global noise with ID {global_noise_id} not found.",
                status_code=status.HTTP_404_NOT_FOUND,
                message="Global noise not found."
            )
        
        noise.deleted_at = datetime.utcnow()
        db.commit()
        return {}
    except Exception as e:
        raise APIException(
            module="delete_global_noise",
            error=str(e),
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            message="Error deleting global noise."
        )
        
async def check_existing_global_noise(
    db: Session, 
    noise: str
) -> bool:
    """
    Check if a global noise entry with the given noise already exists.
    """
    try:
        existing_noise = db.query(GlobalNoise).filter(
            GlobalNoise.noise == noise,
            GlobalNoise.deleted_at.is_(None)
        ).first()
        return existing_noise is not None
    except Exception as e:
        raise APIException(
            module="check_existing_global_noise",
            error=str(e),
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            message="Error checking existing global noise."
        )