import io
import os
from datetime import datetime, timezone
from typing import Callable, Optional
from uuid import UUID

import pandas as pd
from fastapi import HTTPException, UploadFile, status
from fastapi.responses import FileResponse
from sqlalchemy import String, cast
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from starlette.responses import StreamingResponse

from src.apps.base.models.country import Country
from src.apps.base.models.regions import Region, SubRegion
from src.apps.export_data.schemas.export_data import (
    DownloadFilesCreateSchema,
    DownloadFilesOutputSchema,
    ExportDataFilterSchema,
)
from src.apps.export_data.services.download_file import (
    create_download_file,
    get_all_latest_download_files,
    get_all_latest_uploaded_files,
)
from src.apps.export_data.services.tasks import (
    generate_appellation_export_file,
    generate_bottle_size_export_file,
    generate_colour_substitution_export_file,
    generate_global_noise_export_file,
    generate_mongo_map_export_file,
    generate_producer_keyword_export_file,
    generate_producer_noise_export_file,
    generate_spread_table_export_file,
    generate_varieties_export_file,
    generate_wine_alias_export_file,
    generate_wine_db_export_file,
    generate_wine_keyword_export_file,
    generate_wine_noise_export_file,
    generate_word_elimination_export_file,
    generate_word_reserved_export_file,
    update_appellation_from_csv,
    update_bottle_size_from_csv,
    update_colour_substitution_from_csv,
    update_global_noise_from_csv,
    update_mongo_map_from_csv,
    update_producer_keyword_from_csv,
    update_producer_noise_from_csv,
    update_spread_table_from_csv,
    update_varieties_from_csv,
    update_wine_alias_from_csv,
    update_wine_db_from_csv,
    update_wine_keyword_from_csv,
    update_wine_noise_from_csv,
    update_word_elimination_from_csv,
    update_word_reserved_from_csv,
)
from src.apps.files.schemas.file import CreateFileWithPathSchema
from src.apps.files.services.file import create_file_from_path
from src.core.exceptions import APIException
from src.utils.constants import EXPORT_DATA_SUB_FOLDER
from src.utils.enums import ExportDataTypes, ExportDataTypesFileName


async def generate_download_file(
    db: Session,
    payload: ExportDataFilterSchema,
) -> DownloadFilesOutputSchema:
    """
    Generates and stores a download file based on the provided payload.
    :param db: Database session
    :param payload: ExportDataFilterSchema containing export type and other parameters
    :return: DownloadFilesOutputSchema containing details of the stored download file
    """
    data_stream = await get_export_file(db=db, payload=payload)

    # Read the entire content from the StreamingResponse
    content = ""
    async for chunk in data_stream.body_iterator:
        content += chunk.decode("utf-8")

    # Create and store the file locally
    file_name = f"{ExportDataTypesFileName[payload.type].value}{datetime.now().strftime('%Y-%m-%d-%H%M%S')}.txt"
    download_file = await store_files_locally(
        db=db,
        file_name=file_name,
        content=content,
        file_type=payload.type,
    )

    return download_file


async def get_export_file(
    db: Session,
    payload: ExportDataFilterSchema = ExportDataFilterSchema(),
) -> any:
    """
    Fetches the export file based on the provided payload type.
    :param db: Database session
    :param payload: ExportDataFilterSchema containing export type and other parameters
    """
    match payload.type:
        case ExportDataTypes.WINE_DB:
            task = generate_wine_db_export_file()
            return {"task": task, "status": "queued"}
            # return await get_wine_db_export_file(db=db, payload=payload)
        case ExportDataTypes.MONGO_MAP:
            task = generate_mongo_map_export_file()
            return {"task": task, "status": "queued"}
            # return await get_mongo_map_export_file(db=db, payload=payload)
        case ExportDataTypes.WINE_KEYWORD:
            task = generate_wine_keyword_export_file()
            return {"task": task, "status": "queued"}
            # return await get_wine_keyword_export_file(db=db, payload=payload)
        case ExportDataTypes.PRODUCER_KEYWORD:
            task = generate_producer_keyword_export_file()
            return {"task": task, "status": "queued"}
            # return await get_producer_keyword_export_file(db=db, payload=payload)
        case ExportDataTypes.GLOBAL_NOISE:
            task = generate_global_noise_export_file()
            return {"task": task, "status": "queued"}
            # return await get_global_noise_export_file(db=db, payload=payload)
        case ExportDataTypes.PRODUCER_NOISE:
            task = generate_producer_noise_export_file()
            return {"task": task, "status": "queued"}
            # return await get_producer_noise_export_file(db=db, payload=payload)
        case ExportDataTypes.WINE_NOISE:
            task = generate_wine_noise_export_file()
            return {"task": task, "status": "queued"}
            # return await get_wine_noise_export_file(db=db, payload=payload)
        case ExportDataTypes.SPREAD_TABLE:
            task = generate_spread_table_export_file()
            return {"task": task, "status": "queued"}
            # return await get_wine_noise_export_file(db=db, payload=payload)

        case ExportDataTypes.WORD_RESERVED:
            task = generate_word_reserved_export_file()
            return {"task": task, "status": "queued"}
            # return await get_wine_noise_export_file(db=db, payload=payload)
        case ExportDataTypes.BOTTLE_SIZE:
            task = generate_bottle_size_export_file()
            return {"task": task, "status": "queued"}
            # return await get_wine_noise_export_file(db=db, payload=payload)
        case ExportDataTypes.WINE_ALIAS:
            task = generate_wine_alias_export_file()
            return {"task": task, "status": "queued"}
            # return await get_wine_noise_export_file(db=db, payload=payload)
        case ExportDataTypes.VARIETIES:
            task = generate_varieties_export_file()
            return {"task": task, "status": "queued"}
            # return await get_wine_noise_export_file(db=db, payload=payload)
        case ExportDataTypes.COLOUR_SUBSTITUTIONS:
            task = generate_colour_substitution_export_file()
            return {"task": task, "status": "queued"}
            # return await get_wine_noise_export_file(db=db, payload=payload)
        case ExportDataTypes.WORD_ELIMINATION:
            task = generate_word_elimination_export_file()
            return {"task": task, "status": "queued"}
            # return await get_wine_noise_export_file(db=db, payload=payload)
        case ExportDataTypes.APPELLATION:
            task = generate_appellation_export_file()
            return {"task": task, "status": "queued"}
            # return await get_wine_noise_export_file(db=db, payload=payload)

        case _:
            raise APIException(
                module="get_export_file",
                error={"exception": "Invalid export type"},
                status_code=status.HTTP_400_BAD_REQUEST,
                message="Invalid export type provided.",
            )


async def download_file(
    db: Session,
    payload: ExportDataFilterSchema = ExportDataFilterSchema(),
) -> any:
    """
    Downloads a specific export file based on the provided type.
    Args:
        db (Session): Database session dependency.
        payload (ExportDataFilterSchema): Filter criteria for the export file, including type.
    Returns:
        StreamingResponse: Contains the export file data or an error message.
    """

    latest_files = await get_all_latest_download_files(db=db)

    if not latest_files:
        raise APIException(
            module="download_file",
            error={"exception": "No export files available."},
            status_code=status.HTTP_404_NOT_FOUND,
            message="No export files are available for download.",
        )

    if not latest_files.get(payload.type):
        raise APIException(
            module="download_file",
            error={"exception": f"No export file available for type: {payload.type}."},
            status_code=status.HTTP_404_NOT_FOUND,
            message="The requested export file type does not exist.",
        )
    files_obj = latest_files.get(payload.type)
    path = os.path.join(os.getcwd(), EXPORT_DATA_SUB_FOLDER, "masterdata", files_obj.get("file_name"))

    if not os.path.isfile(path):
        raise APIException(
            module="download_file",
            error={"exception": f"File not found at path: {path}"},
            status_code=status.HTTP_404_NOT_FOUND,
            message="The requested export file does not exist on the server.",
        )

    return FileResponse(
        path=path,
        filename=files_obj.get("file_name"),
        media_type="application/octet-stream",  # or 'text/csv', 'application/pdf', etc.
    )


async def download_file_by_name(
    db: Session,
    file_name: str,
) -> any:
    """
    Downloads a specific export file based on the provided file name.
    Args:
        db (Session): Database session dependency.
        file_name (str): The name of the file to be downloaded.
    Returns:
        StreamingResponse: Contains the export file data or an error message.
    """
    try:
        download_location = os.path.join(os.getcwd(), EXPORT_DATA_SUB_FOLDER, "masterdata")
        path = os.path.join(download_location, file_name)

        if not os.path.isfile(path):
            raise APIException(
                module="download_file_by_name",
                error={"exception": f"File not found at path: {path}"},
                status_code=status.HTTP_404_NOT_FOUND,
                message="The requested file does not exist on the server.",
            )

        return FileResponse(
            path=path,
            filename=file_name,
            media_type="application/octet-stream",  # or 'text/csv', 'application/pdf', etc.
        )
    except Exception as e:
        raise APIException(
            module="download_file_by_name",
            error={"exception": str(e)},
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            message="Failed to download the specified file.",
        )


async def store_files_locally(
    db: Session,
    file_name: str,
    content: Optional[str] = None,
    subfolder: Optional[str] = EXPORT_DATA_SUB_FOLDER,
    file_type: Optional[str] = None,
) -> DownloadFilesOutputSchema:
    """
    Store the given content in a file under the specified subfolder in the current working directory.
    Stores the file in database and returns the DownloadFilesOutputSchema.
    """
    download_location = os.path.join(os.getcwd(), subfolder)
    os.makedirs(download_location, exist_ok=True)  # Ensure the directory exists , if not it will be created
    file_path = os.path.join(download_location, file_name)

    # If content is provided, (over)write the file. If None, assume the file already exists at file_path.
    if content is not None:
        with open(file_path, "w", encoding="utf-8") as f:
            f.write(content)

    # Persist relative path for DB records
    file_path = os.path.join(EXPORT_DATA_SUB_FOLDER, file_name).replace("\\", "/")

    new_file = await create_file_from_path(
        db=db,
        payload=CreateFileWithPathSchema(
            file_path=file_path,
            file_name=file_name,
            mime="text/plain",
        ),
    )

    new_download_file = await create_download_file(
        db=db, payload=DownloadFilesCreateSchema(file_id=new_file.id, file_type=file_type)
    )

    return new_download_file


async def save_uploaded_file(
    db: Session,
    file: UploadFile,
    payload: ExportDataFilterSchema = ExportDataFilterSchema(),
) -> DownloadFilesOutputSchema:
    """
    Saves an uploaded export file based on the provided type.
    :param db: Database session
    :param payload: ExportDataFilterSchema containing export type and other parameters
    :return: DownloadFilesOutputSchema
    """
    content = await file.read()
    file_content = content.decode("utf-8")

    file_name = f"{ExportDataTypesFileName[payload.type].value}{datetime.now().strftime('%Y-%m-%d-%H%M%S')}.txt"
    upload_location = os.path.join(os.getcwd(), EXPORT_DATA_SUB_FOLDER, "uploaddata")
    os.makedirs(upload_location, exist_ok=True)

    local_path = os.path.join(upload_location, file_name)
    with open(local_path, "w", encoding="utf-8") as f:
        f.write(file_content)

    return {"status": "success", "file_name": file_name, "path": local_path}


async def process_uploaded_file_date(
    db: Session,
    type: ExportDataTypes,
):
    """
    Processes an uploaded export file based on the provided type.
    :param db: Database session
    :param payload: ExportDataFilterSchema containing export type and other parameters
    :return: Task status
    """
    latest_files = await get_all_latest_uploaded_files(db=db)

    if not latest_files:
        raise APIException(
            module="download_file",
            error={"exception": "No export files available."},
            status_code=status.HTTP_404_NOT_FOUND,
            message="No export files are available for download.",
        )

    if not latest_files.get(type):
        raise APIException(
            module="download_file",
            error={"exception": f"No export file available for type: {type}."},
            status_code=status.HTTP_404_NOT_FOUND,
            message="The requested export file type does not exist.",
        )
    files_obj = latest_files.get(type)
    path = os.path.join(os.getcwd(), EXPORT_DATA_SUB_FOLDER, "uploaddata", files_obj.get("file_name"))

    if not os.path.isfile(path):
        raise APIException(
            module="download_file",
            error={"exception": f"File not found at path: {path}"},
            status_code=status.HTTP_404_NOT_FOUND,
            message="The requested export file does not exist on the server.",
        )
    file_name = files_obj.get("file_name")
    match type:
        case ExportDataTypes.WINE_DB:
            task = update_wine_db_from_csv(file_name=file_name)
            return {"task": task, "status": "queued"}
            # return await process_uploaded_wine_db_file(db=db)
        case ExportDataTypes.MONGO_MAP:
            task = update_mongo_map_from_csv(file_name=file_name)
            return {"task": task, "status": "queued"}
            # return await process_uploaded_mongo_map_file(db=db)
        case ExportDataTypes.WINE_KEYWORD:
            task = update_wine_keyword_from_csv(file_name=file_name)
            return {"task": task, "status": "queued"}
            # return await process_uploaded_wine_keyword_file(db=db)
        case ExportDataTypes.PRODUCER_KEYWORD:
            task = update_producer_keyword_from_csv(file_name=file_name)
            return {"task": task, "status": "queued"}
            # return await process_uploaded_producer_keyword_file(db=db)
        case ExportDataTypes.GLOBAL_NOISE:
            task = update_global_noise_from_csv(file_name=file_name)
            return {"task": task, "status": "queued"}
            # return await process_uploaded_global_noise_file(db=db)
        case ExportDataTypes.PRODUCER_NOISE:
            task = update_producer_noise_from_csv(file_name=file_name)
            return {"task": task, "status": "queued"}
            # return await process_uploaded_producer_noise_file(db=db)
        case ExportDataTypes.WINE_NOISE:
            task = update_wine_noise_from_csv(file_name=file_name)
            return {"task": task, "status": "queued"}
            # return await process_uploaded_wine_noise_file(db=db)
        case ExportDataTypes.SPREAD_TABLE:
            task = update_spread_table_from_csv(file_name=file_name)
            return {"task": task, "status": "queued"}
            # return await process_uploaded_wine_noise_file(db=db)

        case ExportDataTypes.WORD_RESERVED:
            task = update_word_reserved_from_csv(file_name=file_name)
            return {"task": task, "status": "queued"}
            # return await process_uploaded_wine_noise_file(db=db)
        case ExportDataTypes.BOTTLE_SIZE:
            task = update_bottle_size_from_csv(file_name=file_name)
            return {"task": task, "status": "queued"}
            # return await process_uploaded_wine_noise_file(db=db)
        case ExportDataTypes.WINE_ALIAS:
            task = update_wine_alias_from_csv(file_name=file_name)
            return {"task": task, "status": "queued"}
            # return await process_uploaded_wine_noise_file(db=db)
        case ExportDataTypes.VARIETIES:
            task = update_varieties_from_csv(file_name=file_name)
            return {"task": task, "status": "queued"}
            # return await process_uploaded_wine_noise_file(db=db)
        case ExportDataTypes.COLOUR_SUBSTITUTIONS:
            task = update_colour_substitution_from_csv(file_name=file_name)
            return {"task": task, "status": "queued"}
            # return await process_uploaded_wine_noise_file(db=db)
        case ExportDataTypes.WORD_ELIMINATION:
            task = update_word_elimination_from_csv(file_name=file_name)
            return {"task": task, "status": "queued"}
            # return await process_uploaded_wine_noise_file(db=db)
        case ExportDataTypes.APPELLATION:
            task = update_appellation_from_csv(file_name=file_name)
            return {"task": task, "status": "queued"}
            # return await process_uploaded_wine_noise_file(db=db)

        case _:
            raise APIException(
                module="process_uploaded_file_date",
                error={"exception": "Invalid export type provided."},
                status_code=status.HTTP_400_BAD_REQUEST,
                message="The provided export type is not recognized.",
            )


async def download_matched_file(db: Session, code: str, date: str):
    try:
        file_name = f"{code}-{date}-match.txt"
        path = os.path.join(os.getcwd(), "uploads", "match_outputs", file_name)

        if not os.path.isfile(path):
            raise APIException(
                module="download_file",
                error={"exception": f"File not found at path: {path}"},
                status_code=status.HTTP_404_NOT_FOUND,
                message="The requested export file does not exist on the server.",
            )

        return FileResponse(
            path=path,
            filename=file_name,
            media_type="application/octet-stream",  # or 'text/csv', 'application/pdf', etc.
        )
    except Exception as e:
        return {"status": "failed", "error": str(e)}


async def download_history_file(
    db: Session,
    file_name: str,
):
    try:
        path = os.path.join(os.getcwd(), "uploads", "history", file_name)

        if not os.path.isfile(path):
            raise APIException(
                module="download_file",
                error={"exception": f"File not found at path: {path}"},
                status_code=status.HTTP_404_NOT_FOUND,
                message="The requested export file does not exist on the server.",
            )

        return FileResponse(
            path=path,
            filename=file_name,
            media_type="application/octet-stream",  # or 'text/csv', 'application/pdf', etc.
        )
    except Exception as e:
        return {"status": "failed", "error": str(e)}
