import logging
import uuid

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

from app.library.cache import Cache
from app.library.conditions import Condition, Conditions
from app.library.config import Config
from app.library.encoder import Encoder
from app.library.Events import EventBus, Events
from app.library.router import route
from app.library.Utils import extract_info, init_class, validate_uuid
from app.library.YTDLPOpts import YTDLPOpts

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


@route("GET", "api/conditions/", "conditions_list")
async def conditions_list(encoder: Encoder) -> Response:
    """
    Get the conditions

    Args:
        encoder (Encoder): The encoder instance.

    Returns:
        Response: The response object.

    """
    return web.json_response(
        data=Conditions.get_instance().get_all(),
        status=web.HTTPOk.status_code,
        dumps=encoder.encode,
    )


@route("PUT", "api/conditions/", "conditions_add")
async def conditions_add(request: Request, encoder: Encoder, notify: EventBus) -> Response:
    """
    Save Conditions.

    Args:
        request (Request): The request object.
        encoder (Encoder): The encoder instance.
        notify (EventBus): The event bus instance.

    Returns:
        Response: The response object

    """
    data = await request.json()

    if not isinstance(data, list):
        return web.json_response(
            {"error": "Invalid request body expecting list with dicts."},
            status=web.HTTPBadRequest.status_code,
        )

    items: list = []

    cls = Conditions.get_instance()

    for item in data:
        if not isinstance(item, dict):
            return web.json_response(
                {"error": "Invalid request body expecting list with dicts."},
                status=web.HTTPBadRequest.status_code,
            )

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

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

        if not item.get("cli"):
            return web.json_response(
                {"error": "command options for yt-dlp is required.", "data": item},
                status=web.HTTPBadRequest.status_code,
            )

        if not item.get("id", None) or not validate_uuid(item.get("id"), version=4):
            item["id"] = str(uuid.uuid4())

        try:
            cls.validate(item)
        except ValueError as e:
            return web.json_response(
                {"error": f"Failed to validate condition '{item.get('name')}'. '{e!s}'"},
                status=web.HTTPBadRequest.status_code,
            )

        items.append(init_class(Condition, item))

    try:
        items = cls.save(items=items).load().get_all()
    except Exception as e:
        LOG.exception(e)
        return web.json_response(
            {"error": "Failed to save conditions.", "message": str(e)},
            status=web.HTTPInternalServerError.status_code,
        )

    await notify.emit(Events.CONDITIONS_UPDATE, data=items)
    return web.json_response(data=items, status=web.HTTPOk.status_code, dumps=encoder.encode)


@route("POST", "api/conditions/test/", "conditions_test")
async def conditions_test(request: Request, encoder: Encoder, cache: Cache, config: Config) -> Response:
    """
    Test condition against URL.

    Args:
        request (Request): The request object.
        encoder (Encoder): The encoder instance.
        cache (Cache): The cache instance.
        config (Config): The configuration instance.

    Returns:
        Response: The response object

    """
    params = await request.json()

    if not isinstance(params, dict):
        return web.json_response(
            {"error": "Invalid request body expecting dict."},
            status=web.HTTPBadRequest.status_code,
        )

    url: str | None = params.get("url")
    if not url:
        return web.json_response({"error": "url is required."}, status=web.HTTPBadRequest.status_code)

    cond: str | None = params.get("condition")
    if not cond:
        return web.json_response({"error": "condition is required."}, status=web.HTTPBadRequest.status_code)

    try:
        preset: str = params.get("preset", config.default_preset)
        key: str = cache.hash(url + str(preset))
        if not cache.has(key):
            opts = {}
            if ytdlp_proxy := config.get_ytdlp_args().get("proxy", None):
                opts["proxy"] = ytdlp_proxy
            ytdlp_opts = YTDLPOpts.get_instance().preset(name=preset).add(opts).get_all()

            data = extract_info(
                config=ytdlp_opts,
                url=url,
                debug=False,
                no_archive=True,
                follow_redirect=True,
                sanitize_info=True,
            )
            if not data:
                return web.json_response(
                    data={"error": f"Failed to extract info from '{url!s}'."},
                    status=web.HTTPBadRequest.status_code,
                )
            cache.set(key=key, value=data, ttl=600)
        else:
            data = cache.get(key)
    except Exception as e:
        LOG.exception(e)
        return web.json_response(
            data={"error": f"Failed to extract video info. '{e!s}'"},
            status=web.HTTPInternalServerError.status_code,
        )

    try:
        from yt_dlp.utils import match_str

        status = match_str(cond, data)
    except Exception as e:
        LOG.exception(e)
        return web.json_response(
            data={"error": str(e)},
            status=web.HTTPBadRequest.status_code,
        )

    return web.json_response(
        data={"status": status, "condition": cond, "data": data},
        status=web.HTTPOk.status_code,
        dumps=encoder.encode,
    )
