import logging
from datetime import UTC, datetime
from pathlib import Path
from typing import TYPE_CHECKING

import anyio
from aiohttp import web
from aiohttp.web import Request, Response

from app.library.config import Config
from app.library.Download import Download
from app.library.DownloadQueue import DownloadQueue
from app.library.router import route
from app.library.Utils import is_downloaded, remove_from_archive
from app.library.YTDLPOpts import YTDLPOpts

if TYPE_CHECKING:
    from library.Download import Download

LOG: logging.Logger = logging.getLogger(__name__)


@route("DELETE", r"api/archive/{id}", "archive.remove")
async def archive_remove(request: Request, queue: DownloadQueue, config: Config) -> Response:
    """
    Remove an item from the archive.

    Args:
        request (Request): The request object.
        queue (DownloadQueue): The download queue instance.
        config (Config): The configuration instance.

    Returns:
        Response: The response object.

    """
    item = None
    try:
        data: dict | None = await request.json()
    except Exception:
        data = {}

    title: str = ""

    url: str | None = data.get("url", None) if data else None

    if not url:
        id: str = request.match_info.get("id")
        if not id:
            return web.json_response(data={"error": "id is required."}, status=web.HTTPBadRequest.status_code)

        try:
            item: Download | None = queue.done.get_by_id(id)
            if not item:
                return web.json_response(data={"error": f"item '{id}' not found."}, status=web.HTTPNotFound.status_code)

            url = item.info.url
            title = f" '{item.info.title}'"
        except KeyError:
            return web.json_response(data={"error": f"item '{id}' not found."}, status=web.HTTPNotFound.status_code)

    if config.manual_archive:
        remove_from_archive(archive_file=Path(config.manual_archive), url=url)

    archive_file: Path | None = Path(config.archive_file) if config.keep_archive else None
    if item:
        params: YTDLPOpts = YTDLPOpts.get_instance().preset(name=item.info.preset)
        if item.info.cli:
            params.add_cli(item.info.cli, from_user=True)

        params = params.get_all()
        if user_file := params.get("download_archive", None):
            archive_file = Path(user_file)

    if not archive_file:
        return web.json_response(
            data={
                "error": "Archive file is not configured." if not config.keep_archive else "Archive file is not set."
            },
            status=web.HTTPBadRequest.status_code,
        )

    if not archive_file.exists():
        return web.json_response(
            data={"error": f"Archive file '{archive_file}' does not exist."},
            status=web.HTTPNotFound.status_code,
        )

    if not remove_from_archive(archive_file=archive_file, url=url):
        return web.json_response(
            data={"error": f"item{title} not found in '{archive_file}' archive."},
            status=web.HTTPNotFound.status_code,
        )

    return web.json_response(
        data={"message": f"item{title} removed from '{archive_file}' archive."},
        status=web.HTTPOk.status_code,
    )


@route("POST", r"api/archive/{id}", "archive.item")
async def archive_item(request: Request, queue: DownloadQueue, config: Config):
    """
    Manually mark an item as archived.

    Args:
        request (Request): The request object.
        queue (DownloadQueue): The download queue instance.
        config (Config): The configuration instance.

    Returns:
        Response: The response object.

    """
    id: str = request.match_info.get("id")

    if not id:
        return web.json_response(data={"error": "id is required."}, status=web.HTTPBadRequest.status_code)

    try:
        item: Download | None = queue.done.get_by_id(id)
        if not item:
            return web.json_response(data={"error": "item not found."}, status=web.HTTPNotFound.status_code)
    except KeyError:
        return web.json_response(data={"error": "item not found."}, status=web.HTTPNotFound.status_code)

    if config.manual_archive:
        manual_archive = Path(config.manual_archive)
        if manual_archive.exists():
            exists, idDict = is_downloaded(manual_archive, item.info.url)
            if exists is False and idDict.get("archive_id"):
                async with await anyio.open_file(manual_archive, "a") as f:
                    await f.write(f"{idDict['archive_id']} - at: {datetime.now(UTC).isoformat()}\n")

    params: YTDLPOpts = YTDLPOpts.get_instance().preset(name=item.info.preset)
    if item.info.cli:
        params.add_cli(item.info.cli, from_user=True)

    params = params.get_all()

    user_file: str | None = params.get("download_archive", None)
    archive_file: Path = Path(user_file) if user_file else Path(config.archive_file)
    if not archive_file.exists():
        return web.json_response(
            data={"error": f"Archive file '{archive_file}' does not exist."},
            status=web.HTTPNotFound.status_code,
        )

    exists, idDict = is_downloaded(archive_file, item.info.url)
    item_id: str | None = idDict.get("archive_id")
    if not item_id:
        return web.json_response(
            data={"error": "item does not have an archive ID."}, status=web.HTTPBadRequest.status_code
        )

    if exists is True:
        return web.json_response(
            data={"error": f"item '{item_id}' already archived in file '{archive_file}'."},
            status=web.HTTPConflict.status_code,
        )

    async with await anyio.open_file(archive_file, "a") as f:
        await f.write(f"{item_id}\n")

    return web.json_response(
        data={"message": f"item '{item_id}' archived in file '{archive_file}'."},
        status=web.HTTPOk.status_code,
    )
