from http import HTTPStatus
from typing import Annotated, override

from fastapi import Response, Security
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse

from phederation.api.routes.base import BaseRoute
from phederation.federation.discovery import InstanceInfo
from phederation.models import (
    APActivity,
    ErrorMessageActivityHandler,
    ErrorMessageActorUnauthorized,
    RelativeLink,
)
from phederation.security.authentication import UserInfo
from phederation.utils import NodeInfo, ObjectId
from phederation.utils.base import UrlType, assemble_id_url
from phederation.utils.exceptions import ActivityPubException, ResolverError


class InstanceRoute(BaseRoute):

    @override
    def setup(self):
        @self.router.get("/health", tags=["instance"])
        def health_check():  # pyright: ignore[reportUnusedFunction]
            return {"status": "ok", "description": "The API is available"}

        @self.router.get("/robots.txt", tags=["instance"])
        def get_robots_txt():  # pyright: ignore[reportUnusedFunction]
            """Get robots allow/disallow file."""
            with open("settings/robots.txt", "r") as robots_txt_file:
                return Response(
                    content="\n".join(robots_txt_file.readlines()),
                    status_code=HTTPStatus.OK,
                    media_type="text/plain",
                )

        @self.router.get("/.well-known/instanceinfo", tags=["instance"])
        async def get_instanceinfo() -> InstanceInfo:  # pyright: ignore[reportUnusedFunction]
            """Get NodeInfo links."""
            return await self.server.instance.get_instance_info()

        @self.router.get("/.well-known/nodeinfo", tags=["instance"])
        def get_nodeinfo_links() -> dict[str, list[RelativeLink]]:  # pyright: ignore[reportUnusedFunction]
            """Get NodeInfo links."""
            return self.server.instance.get_nodeinfo_links()

        @self.router.get("/nodeinfo/2.1", tags=["instance"])
        async def get_nodeinfo() -> NodeInfo:  # pyright: ignore[reportUnusedFunction]
            """Get NodeInfo."""
            return await self.server.instance.get_nodeinfo()

        @self.router.get("/nodeinfo/{primary}", tags=["instance"])
        async def get_nodeinfo_from_primary(primary: str):  # pyright: ignore[reportUnusedFunction]
            """Get NodeInfo stored in storage."""
            nodeinfo_id = assemble_id_url(type=UrlType.NodeInfo, base_url=self.api.settings.domain.base_url, primary=primary)
            node_info = await self.server.storage.node_info.read(id=nodeinfo_id)
            if not node_info:
                return JSONResponse(
                    content={"status": "error", "description": "Node info document not found"},
                    status_code=HTTPStatus.NOT_FOUND,
                )
            node_info.id = nodeinfo_id
            return node_info

        @self.router.get("/ns/schema/2.1", tags=["instance"])
        def get_nodeinfo_schema():  # pyright: ignore[reportUnusedFunction]
            """Get NodeInfo schema."""
            return JSONResponse(
                content=jsonable_encoder(self.server.instance.get_nodeinfo_schema()),
                status_code=HTTPStatus.OK,
            )

        @self.router.post(
            "/inbox",
            tags=["instance", "users"],
            responses={
                HTTPStatus.INTERNAL_SERVER_ERROR: {
                    "model": ErrorMessageActivityHandler,
                    "description": ErrorMessageActivityHandler().description,
                },
                HTTPStatus.UNAUTHORIZED: {
                    "model": ErrorMessageActorUnauthorized,
                    "description": ErrorMessageActorUnauthorized().description,
                },
            },
        )
        async def post_shared_inbox(activity: APActivity):  # pyright: ignore[reportUnusedFunction]
            """Handle shared inbox."""
            # Handle activity
            try:
                self.logger.debug(f"Shared inbox received activity. Processing.")

                # handle the shared inbox
                result = await self.server.handle_inbox(activity=activity)
            except ActivityPubException as e:
                self.logger.debug(f"Shared inbox post fails. Error: '{e.message}'. Failing silently.")
                return JSONResponse(
                    content={"Status": "Error", "Description": (e.user_message or "Unknown error") + "; Failing silently"},
                    status_code=HTTPStatus.CONTINUE,
                    media_type="application/activity+json",
                )

            result_id: ObjectId | None = result.get_success_id()
            if result.success and result_id:
                return JSONResponse(
                    content={"Status": "OK"},
                    status_code=HTTPStatus.CREATED,
                    media_type="application/activity+json",
                    headers={"Location": result_id},
                )
            else:
                self.logger.warning(f"Instance shared inbox processing error: {result}")
                return JSONResponse(
                    status_code=HTTPStatus.BAD_REQUEST,
                    content={"Status": "error", "Description": "Shared inbox processing failed"},
                    media_type="application/activity+json",
                )

        @self.router.post(
            f"/{UrlType.Resolve.value}",
            tags=["instance"],
            responses={
                HTTPStatus.INTERNAL_SERVER_ERROR: {
                    "model": ErrorMessageActivityHandler,
                    "description": ErrorMessageActivityHandler().description,
                },
                HTTPStatus.UNAUTHORIZED: {
                    "model": ErrorMessageActorUnauthorized,
                    "description": ErrorMessageActorUnauthorized().description,
                },
            },
        )
        async def post_resolve_ids(  # pyright: ignore[reportUnusedFunction]
            ids_to_resolve: list[ObjectId],
            user_info: Annotated[UserInfo | None, Security(self.middleware.authentication, scopes=["user"])],
        ):
            """Resolve multiple objects from this instance in a json list."""
            try:
                self.logger.debug(f"Resolve endpoint received {len(ids_to_resolve)} ids. Processing.")
                if "resolve" not in self.server.settings.federation.endpoints:
                    raise ResolverError("Endpoint 'resolve' is not activated on this instance, cannot resolve multiple ids at once")
                if len(ids_to_resolve) > self.server.settings.federation.max_concurrent_resolve:
                    raise ResolverError(
                        f"Endpoint 'resolve' can only resolve {self.server.settings.federation.max_concurrent_resolve} ids at once (requested: {len(ids_to_resolve)})"
                    )

                result = await self.server.resolve_multiple(ids_to_resolve, actor_for_authorization=user_info.actor_id if user_info else None)
                self.logger.debug(f"Resolved {len(result)} objects out of {len(ids_to_resolve)}")
            except ActivityPubException as e:
                self.logger.debug(f"Resolve endpoint POST failed. Error: '{e.message}'. Failing silently.")
                return JSONResponse(
                    content={"Status": "Error", "Description": (e.user_message or "Unknown error") + "; Failing silently"},
                    status_code=HTTPStatus.CONTINUE,
                    media_type="application/activity+json",
                )

            return JSONResponse(
                content=[obj.serialize() for obj in result],
                status_code=HTTPStatus.CREATED,
                media_type="application/activity+json",
            )
