# -*- coding: utf-8 -*-
#
# Copyright 2021-2025 NXP
#
# SPDX-License-Identifier: MIT


import asyncio
import json
import logging
from contextlib import suppress
from string import Template

import aiohttp
import flatdict
from aiohttp import web

from fc_common.version import get_runtime_version
from fc_server.core import AsyncRunMixin
from fc_server.core.config import Config


class ApiSvr(AsyncRunMixin):
    """
    Rest api server
    """

    def __init__(self, context):
        self.context = context
        self.external_info_tool = Config.api_server.get("external_info_tool", "")
        self.logger = logging.getLogger("fc_server")

    @staticmethod
    def friendly_status(status):
        with suppress(Exception):
            return Config.frameworks_config[status]["friendly_status"]
        return status

    async def resource_status(self, request):
        return await self.fetch_resource_status(request)

    async def fetch_resource_status(
        self, request
    ):  # pylint: disable=too-many-branches, too-many-locals, too-many-statements
        res = request.match_info.get("res", "")

        params = request.rel_url.query
        verbose = int(params.get("verbose", "0"))
        farm_type = params.get("farmtype", "")
        device_type = params.get("devicetype", "")
        peripheral_info = params.get("peripheralinfo", "")

        # get all labgrid managed resources
        labgrid_managed_resources = []
        comments = {}
        overlay_comments = {}
        for framework in self.context.framework_instances:
            if framework.__module__.split(".")[-1] == "labgrid":
                labgrid_managed_resources = framework.managed_resources
                if verbose >= 1:
                    comments = await framework.labgrid_get_comments()
            if framework.__module__.split(".")[-1] == "lava":
                if verbose >= 1:
                    overlay_comments = await framework.get_comments(self.context)
        comments.update(overlay_comments)

        if device_type and farm_type:
            scope = Config.raw_managed_resources.get(farm_type, {}).get(device_type, [])
        elif device_type:
            scope = []
            for raw_managed_resource in Config.raw_managed_resources.values():
                scope += raw_managed_resource.get(device_type, [])
        elif farm_type:
            scope = flatdict.FlatterDict(
                Config.raw_managed_resources.get(farm_type, {})
            ).values()
        else:
            scope = ["all"]

        if res:
            if scope == ["all"]:
                scope = [res]
            else:
                if res in scope:
                    scope = [res]
                else:
                    scope = []

        resources_info = []
        tool_command_list = []
        for resource, status in self.context.managed_resources_status.items():
            if scope != ["all"] and resource not in scope:
                continue

            item = []
            item.append(resource)
            item.append(Config.managed_resources_farm_types.get(resource, ""))
            item.append(ApiSvr.friendly_status(status))
            if resource not in labgrid_managed_resources:
                item.append("non-debuggable")
            else:
                item.append("")

            if verbose >= 1:
                try:
                    item.append(comments[resource])
                except KeyError:
                    item.append("")

            resources_info.append(item)

            # fetch external resource info if needed
            if self.external_info_tool and (verbose >= 2 or peripheral_info):
                fc_resource = resource
                fc_farm_type = Config.managed_resources_farm_types.get(resource, "")
                template = Template(self.external_info_tool)
                tool_command = template.substitute(
                    {
                        "fc_resource": fc_resource,
                        "fc_farm_type": fc_farm_type,
                        "fc_peripheral_info": peripheral_info,
                    }
                )
                tool_command_list.append(self._run_cmd(tool_command))

        fc_not_match_list = []
        if self.external_info_tool and (verbose >= 2 or peripheral_info):
            external_info_list = await asyncio.gather(*tool_command_list)
            for index, value in enumerate(external_info_list):
                if value[0] == 0:
                    if verbose >= 2:
                        resources_info[index].append(value[1])
                    if value[1].strip() == "FC_NOT_MATCH":
                        fc_not_match_list.append(index)
                else:
                    if verbose >= 2:
                        resources_info[index].append("NA")
        elif not self.external_info_tool and (
            verbose >= 2 or peripheral_info
        ):  # for fc instance which doesn't configure external_info_tool
            for index, resource in enumerate(resources_info):
                if verbose >= 2:
                    resource.append([])
                if (
                    peripheral_info
                ):  # external_info_tool not specified instance defintly not match anything
                    fc_not_match_list.append(index)

        resources_info = [
            item
            for index, item in enumerate(resources_info)
            if index not in fc_not_match_list
        ]

        return web.json_response(resources_info)

    @staticmethod
    async def pong(_):
        return web.Response(text="pong")

    @staticmethod
    async def server_info(_):
        return web.Response(text=f"v{get_runtime_version('fc-server')}")

    async def all_locks(self, _):
        all_locks_info = [
            framework.lock_info(self.context)
            for framework in self.context.framework_instances
        ]
        all_locks_infos = await asyncio.gather(*all_locks_info)
        all_locks_infos = filter(lambda _: isinstance(_, str), all_locks_infos)

        return web.Response(text="\n".join(all_locks_infos))

    async def kick_booked_resource(self, request):  # pylint: disable=too-many-locals
        if not Config.enable_booking:
            return web.json_response(
                {"ret": 1, "reason": "booking not enabled for this instance"}
            )

        json_data = await request.json()
        self.logger.info(json_data)

        resource = json_data.get("resource", None)
        user = json_data.get("user", None)

        if not user:
            return web.json_response({"ret": 1, "reason": "no user specified"})

        if not resource:
            return web.json_response({"ret": 1, "reason": "no resource specified"})

        url = f"{Config.booking_system}/api/ongoing_booking/"
        timeout = aiohttp.ClientTimeout(total=5)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            try:
                async with session.get(url) as response:
                    output = await response.text()
                    json_output = json.loads(output)
                    res = json_output.get(resource, None)
                    if res:
                        allowd_user = res.get("user", "")
                        if allowd_user != user:
                            return web.json_response(
                                {"ret": 1, "reason": f"not {user}'s slot"}
                            )
            except Exception as exc:  # pylint: disable=broad-except
                self.logger.info(exc)
                return web.json_response({"ret": 1, "reason": str(exc)})

        ret, reason = await self.context.kick_booked_resource(resource)

        return web.json_response({"ret": ret, "reason": reason})

    async def start(self, **svr_args):
        app = web.Application()

        access_logger = logging.getLogger("aiohttp.access")
        access_logger.setLevel(logging.WARN)
        handler = logging.StreamHandler()
        s_format = logging.Formatter(
            fmt="%(asctime)s %(message)s",
            datefmt="%Y-%m-%d %H:%M:%S",
        )
        handler.setFormatter(s_format)
        access_logger.addHandler(handler)

        app.add_routes([web.get("/ping", self.pong)])
        app.add_routes([web.get("/server_info", self.server_info)])
        app.add_routes([web.get("/all_locks", self.all_locks)])
        app.add_routes([web.get("/resource", self.resource_status)])
        app.add_routes([web.get("/resource/{res}", self.resource_status)])
        app.add_routes([web.put("/booked_resource", self.kick_booked_resource)])

        app_runner = web.AppRunner(app)
        await app_runner.setup()

        api_interface = "0.0.0.0"
        api_port = svr_args["port"]
        loop = asyncio.get_event_loop()
        await loop.create_server(app_runner.server, api_interface, api_port)
        self.logger.info("Api Server ready at http://%s:%d.", api_interface, api_port)
