from urllib.parse import quote
from uuid import UUID

from flask import Response, request
from flask_restx import Resource
from pydantic import BaseModel, Field
from werkzeug.exceptions import Forbidden, NotFound

from controllers.common.errors import UnsupportedFileTypeError
from controllers.common.file_response import enforce_download_for_html
from controllers.common.schema import register_schema_models
from controllers.files import files_ns
from core.tools.signature import verify_tool_file_signature
from core.tools.tool_file_manager import ToolFileManager


class ToolFileQuery(BaseModel):
    timestamp: str = Field(..., description="Unix timestamp")
    nonce: str = Field(..., description="Random nonce")
    sign: str = Field(..., description="HMAC signature")
    as_attachment: bool = Field(default=False, description="Download as attachment")


register_schema_models(files_ns, ToolFileQuery)


@files_ns.route("/tools/<uuid:file_id>.<string:extension>")
class ToolFileApi(Resource):
    @files_ns.doc("get_tool_file")
    @files_ns.doc(description="Download a tool file by ID using signed parameters")
    @files_ns.doc(
        params={
            "file_id": "Tool file identifier",
            "extension": "Expected file extension",
            "timestamp": "Unix timestamp used in the signature",
            "nonce": "Random string used in the signature",
            "sign": "HMAC signature verifying the request",
            "as_attachment": "Whether to download the file as an attachment",
        }
    )
    @files_ns.doc(
        responses={
            200: "Tool file stream returned successfully",
            403: "Forbidden - invalid signature",
            404: "File not found",
            415: "Unsupported file type",
        }
    )
    def get(self, file_id: UUID, extension: str):
        file_id_str = str(file_id)

        args = ToolFileQuery.model_validate(request.args.to_dict())
        if not verify_tool_file_signature(
            file_id=file_id_str, timestamp=args.timestamp, nonce=args.nonce, sign=args.sign
        ):
            raise Forbidden("Invalid request.")

        try:
            tool_file_manager = ToolFileManager()
            stream, tool_file = tool_file_manager.get_file_generator_by_tool_file_id(
                file_id_str,
            )

            if not stream or not tool_file:
                raise NotFound("file is not found")

        except NotFound:
            raise

        except Exception:
            raise UnsupportedFileTypeError()

        mime_type = tool_file.mime_type
        filename = tool_file.filename

        response = Response(
            stream,
            mimetype=mime_type,
            direct_passthrough=True,
            headers={},
        )
        if tool_file.size > 0:
            response.headers["Content-Length"] = str(tool_file.size)
        if args.as_attachment and filename:
            encoded_filename = quote(filename)
            response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{encoded_filename}"

        enforce_download_for_html(
            response,
            mime_type=mime_type,
            filename=filename,
            extension=extension,
        )

        return response
