from functools import partial

from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase

from ichec_django_core.models import Member


class AuthAPITestCase(APITestCase):
    def authenticate(self, username: str):
        user = User.objects.get(username=username)
        self.client.force_authenticate(user=user)

    def deauthenticate(self):
        self.client.force_authenticate(user=None)

    def authenticated_op(self, username: str, op):
        self.authenticate(username)
        response = op()
        self.deauthenticate()
        return response

    def get_member_url(self, name):
        user = User.objects.get(username=name)
        self.authenticate(name)
        resp = self.client.get(f"/api/members/{user.id}/", format="json")
        self.deauthenticate()
        return resp.data["url"]

    def get_user(self, name: str) -> Member:
        return Member.objects.get(username=name)

    def public_list(self):
        return self.client.get(self.url, format="json")

    def auth_list(self, username: str):
        return self.authenticated_op(username, self.public_list)

    def detail(self, resource_id: int):
        return self.client.get(self.url + f"{resource_id}/", format="json")

    def auth_detail(self, username: str, resource_id: int):
        return self.authenticated_op(username, partial(self.detail, resource_id))

    def create(self, data: dict, format="json"):
        return self.client.post(self.url, data, format=format)

    def auth_create(self, username: str, data: dict, format="json"):
        return self.authenticated_op(username, partial(self.create, data, format))

    def update(self, resource_id: int, data: dict, format="json"):
        return self.client.put(self.url + f"{resource_id}/", data, format=format)

    def auth_update(self, username: str, resource_id: int, data: dict):
        return self.authenticated_op(username, partial(self.update, resource_id, data))

    def delete(self, resource_id: int):
        return self.client.delete(self.url + f"{resource_id}/")

    def auth_delete(self, username: str, resource_id: int):
        return self.authenticated_op(username, partial(self.delete, resource_id))

    def put_file(self, resource_id: int, field: str, data, filename):
        return self.client.put(
            f"{self.url}{resource_id}/{field}/upload",
            data,
            headers={"Content-Disposition": f'attachment; filename="{filename}"'},
        )

    def auth_put_file(
        self, username: str, resource_id: int, field: str, data, filename: str
    ):
        return self.authenticated_op(
            username, partial(self.put_file, resource_id, field, data, filename)
        )

    def assert_status(self, response, code, msg=""):
        match code:
            case 200:
                return self.assert_200(response, msg)
            case 201:
                return self.assert_201(response, msg)
            case 204:
                self.assert_204(response, msg)
            case 401:
                self.assert_401(response, msg)
            case 403:
                self.assert_403(response, msg)
            case 404:
                self.assert_404(response, msg)

    def assert_200(self, response, msg=""):
        self.assertEqual(
            response.status_code,
            status.HTTP_200_OK,
            msg + " | Response: " + str(response.data),
        )
        return response.data

    def assert_201(self, response, msg=""):
        self.assertEqual(
            response.status_code,
            status.HTTP_201_CREATED,
            msg + " | Response: " + str(response.data),
        )
        return response.data

    def assert_204(self, response, msg=""):
        self.assertEqual(
            response.status_code,
            status.HTTP_204_NO_CONTENT,
            msg + " | Response: " + str(response.data),
        )

    def assert_401(self, response, msg=""):
        self.assertEqual(
            response.status_code,
            status.HTTP_401_UNAUTHORIZED,
            msg + " | Response: " + str(response.data),
        )

    def assert_403(self, response, msg=""):
        self.assertEqual(
            response.status_code,
            status.HTTP_403_FORBIDDEN,
            msg + " | Response: " + str(response.data),
        )

    def assert_404(self, response, msg=""):
        self.assertEqual(
            response.status_code,
            status.HTTP_404_NOT_FOUND,
            msg + " | Response: " + str(response.data),
        )

    def create_for_update(
        self, creator, members: list | None = None, member_key: str = "members"
    ):

        template = self.template
        if members:
            template[member_key] = [self.get_member_url(m) for m in members]

        created = self.assert_201(self.auth_create(creator, self.template))
        for key, value in self.update_template.items():
            if isinstance(value, dict):
                for sub_key, sub_value in value.items():
                    created[key][sub_key] = sub_value
        return created

    def verify_update(self, updated):
        for key, value in self.update_template.items():
            if isinstance(value, dict):
                for sub_key, sub_value in value.items():
                    self.assertEqual(updated[key][sub_key], sub_value)
