import itertools
import mimetypes
import os
import re
import uuid
from collections.abc import Iterable, Iterator
from contextlib import asynccontextmanager
from pathlib import Path

import aiofiles
import magic
from fastapi import HTTPException, UploadFile
from folkugat_web.config import db
from folkugat_web.dal.sql.sessions import query as sessions_dal
from folkugat_web.dal.sql.temes import links as links_dal
from folkugat_web.dal.sql.temes import scores as scores_dal
from folkugat_web.log import logger


async def get_mimetype(upload_file: UploadFile) -> str:
    info = magic.detect_from_content(await upload_file.read(2048))
    await upload_file.seek(0)
    return info.mime_type


IMAGE_MIMETYPE = re.compile(r"image/.+")
PDF_MIMETYPE = re.compile(r".+/pdf")


def check_mimetype(mimetype: str, accepted_mimetypes: Iterable[re.Pattern[str]]) -> None:
    if not any(regex.match(mimetype) for regex in accepted_mimetypes):
        raise HTTPException(status_code=400, detail=f"Unsupported file type: {mimetype}")


def check_upload_file_size(upload_file: UploadFile) -> None:
    if not upload_file.size:
        raise HTTPException(status_code=400, detail="Couldn't find out the size of the file")
    if upload_file.size > db.FILE_MAX_SIZE:
        raise HTTPException(
            status_code=400,
            detail=f"The uploaded file is too big (max size = {db.FILE_MAX_SIZE} bytes)",
        )


def get_db_file_path(filepath: Path) -> str:
    return f"{db.DB_FILES_URL}/{filepath.relative_to(db.DB_FILES_DIR)}"


async def store_tema_file(tema_id: int, upload_file: UploadFile) -> str:
    check_upload_file_size(upload_file)
    mimetype = await get_mimetype(upload_file)
    check_mimetype(mimetype, [IMAGE_MIMETYPE, PDF_MIMETYPE])

    extension = mimetypes.guess_extension(mimetype) or ""
    filepath = create_tema_filename(tema_id=tema_id, extension=extension)

    with open(filepath, "wb") as f:
        _ = f.write(await upload_file.read())

    return get_db_file_path(filepath)


async def store_session_cartell(session_id: int, upload_file: UploadFile) -> str:
    check_upload_file_size(upload_file)
    mimetype = await get_mimetype(upload_file)
    check_mimetype(mimetype, [IMAGE_MIMETYPE])

    extension = mimetypes.guess_extension(mimetype) or ""
    filepath = create_cartell_filename(session_id=session_id, extension=extension)

    with open(filepath, "wb") as f:
        _ = f.write(await upload_file.read())

    return get_db_file_path(filepath)


def create_tema_filename(tema_id: int, extension: str = "") -> Path:
    filename = str(uuid.uuid4().hex) + extension
    filedir = db.DB_FILES_TEMA_DIR / str(tema_id)
    filedir.mkdir(parents=True, exist_ok=True)
    filepath = filedir / filename
    return filepath


def create_cartell_filename(session_id: int, extension: str = "") -> Path:
    filename = str(uuid.uuid4().hex) + extension
    filedir = db.DB_FILES_SESSION_DIR / str(session_id) / "cartell"
    filedir.mkdir(parents=True, exist_ok=True)
    filepath = filedir / filename
    return filepath


def create_tmp_filename(extension: str = "") -> Path:
    filename = str(uuid.uuid4().hex) + extension
    filepath = db.DB_FILES_TMP_DIR / filename
    return filepath


def get_set_filename(filename: str) -> Path:
    return db.DB_FILES_SET_DIR / filename


def get_playlist_filename(filename: str) -> Path:
    return db.DB_FILES_PLAYLIST_DIR / filename


@asynccontextmanager
async def tmp_file(content: str):
    input_filename = create_tmp_filename(extension=".ly")
    async with aiofiles.open(input_filename, "w") as f:
        _ = await f.write(content)
    try:
        yield input_filename
    finally:
        if input_filename.exists():
            os.remove(input_filename)


def list_files(tema_id: str) -> list[str]:
    filedir = db.DB_FILES_TEMA_DIR / str(tema_id)
    return [get_db_file_path(f) for f in filedir.iterdir()]


def get_orphan_files() -> Iterator[Path]:
    alive_urls = (
        {link.url for link in links_dal.get_links()}
        | {score.pdf_url for score in scores_dal.get_scores() if score.pdf_url is not None}
        | {score.img_url for score in scores_dal.get_scores() if score.img_url is not None}
        | {score.preview_url for score in scores_dal.get_scores() if score.preview_url is not None}
        | {session.cartell_url for session in sessions_dal.get_sessions() if session.cartell_url}
    )
    return filter(
        lambda p: p.is_file() and get_db_file_path(p) not in alive_urls,
        itertools.chain(
            db.DB_FILES_TEMA_DIR.rglob("*"),
            db.DB_FILES_TMP_DIR.rglob("*"),
        )
    )


def clean_orphan_files():
    for path in get_orphan_files():
        logger.info(f"Deleting the orphan file: {path}")
        os.remove(path)
