"""Upload dispatch — multipart RPC binds files into Upload fields and enforces
the declarative `File(...)` constraints."""

import json
from typing import Annotated

from django.contrib.auth.models import AnonymousUser
from django.core.files.uploadedfile import SimpleUploadedFile
from django.http import HttpRequest
from django.test import RequestFactory, TestCase
from pydantic import BaseModel

from mizan import Upload, File
from mizan.client import client
from mizan.client.executor import function_call_view
from mizan_core.registry import clear_registry, register


class AvatarOut(BaseModel):
    ok: bool
    size: int
    name: str | None = None


class UploadDispatchTests(TestCase):
    def setUp(self):
        clear_registry()
        self.factory = RequestFactory()

    def tearDown(self):
        clear_registry()

    def _register(self):
        @client
        def set_avatar(
            request: HttpRequest,
            user_id: int,
            avatar: Annotated[Upload, File(max_size="1MB", content_types=["image/png"])],
        ) -> AvatarOut:
            return AvatarOut(ok=True, size=avatar.size, name=avatar.filename)

        register(set_avatar, "set_avatar")

    def _post(self, args, files):
        data = {"fn": "set_avatar", "args": json.dumps(args), **files}
        request = self.factory.post("/api/mizan/call/", data)  # multipart
        request.user = AnonymousUser()
        request._dont_enforce_csrf_checks = True
        return function_call_view(request)

    def test_upload_binds_and_executes(self):
        self._register()
        png = SimpleUploadedFile("a.png", b"\x89PNG" + b"x" * 100, content_type="image/png")
        resp = self._post({"user_id": 5}, {"avatar": png})
        self.assertEqual(resp.status_code, 200)
        data = json.loads(resp.content)
        self.assertTrue(data["result"]["ok"])
        self.assertEqual(data["result"]["name"], "a.png")
        self.assertEqual(data["result"]["size"], 104)

    def test_max_size_rejected(self):
        self._register()
        big = SimpleUploadedFile("b.png", b"x" * (2 * 1024 * 1024), content_type="image/png")
        resp = self._post({"user_id": 5}, {"avatar": big})
        self.assertEqual(resp.status_code, 400)
        self.assertIn("max size", resp.content.decode())

    def test_content_type_rejected(self):
        self._register()
        gif = SimpleUploadedFile("c.gif", b"GIF89a", content_type="image/gif")
        resp = self._post({"user_id": 5}, {"avatar": gif})
        self.assertEqual(resp.status_code, 400)
        self.assertIn("content-type", resp.content.decode())
