from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, Permission
from django.core.management import call_command
from rest_framework.exceptions import PermissionDenied
from rest_framework.test import APIClient, APITestCase
from types import SimpleNamespace

from apps.catalogos.models import Oficina
from apps.usuarios.models import Profile
from apps.usuarios.permissions import ADMIN_GROUP_NAME
from utils.office import extract_office_id


User = get_user_model()


class MeEndpointTests(APITestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            username="user1",
            email="user1@example.com",
            password="user12345",
        )

        Profile.objects.get_or_create(user=self.user)

        self.oficina1 = Oficina.objects.create(codigo="OF-1", nombre="Oficina 1", activa=True)
        self.oficina2 = Oficina.objects.create(codigo="OF-2", nombre="Oficina 2", activa=True)
        self.user.profile.oficinas.add(self.oficina1)

    def _login(self, username, password):
        resp = self.client.post(
            "/auth/token/",
            {"username": username, "password": password},
            format="json",
        )
        self.assertEqual(resp.status_code, 200)
        return resp.data["access"]

    def test_me_requires_auth(self):
        resp = self.client.get("/usuarios/me/")
        self.assertEqual(resp.status_code, 401)

    def test_me_get_returns_user_and_telefono(self):
        token = self._login("user1", "user12345")
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token}")

        resp = self.client.get("/usuarios/me/")
        self.assertEqual(resp.status_code, 200)
        self.assertEqual(resp.data["username"], "user1")
        self.assertIn("telefono", resp.data)
        self.assertIn("oficinas", resp.data)
        self.assertEqual(len(resp.data["oficinas"]), 1)
        self.assertEqual(resp.data["oficinas"][0]["id"], self.oficina1.id)

    def test_me_patch_updates_user_and_profile(self):
        token = self._login("user1", "user12345")
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token}")

        resp = self.client.patch(
            "/usuarios/me/",
            {"first_name": "Juan", "telefono": "555-123"},
            format="json",
        )
        self.assertEqual(resp.status_code, 200)

        self.user.refresh_from_db()
        self.assertEqual(self.user.first_name, "Juan")

        profile = Profile.objects.get(user=self.user)
        self.assertEqual(profile.telefono, "555-123")


class UsuariosAdminEndpointsTests(APITestCase):
    def setUp(self):
        from django.contrib.auth.models import Group

        self.admin_group = Group.objects.create(name=ADMIN_GROUP_NAME)

        self.admin = User.objects.create_user(
            username="admin1",
            email="admin1@example.com",
            password="admin12345",
            is_staff=True,
        )
        self.admin.groups.add(self.admin_group)

        self.user = User.objects.create_user(
            username="user2",
            email="user2@example.com",
            password="user12345",
        )

        self.oficina1 = Oficina.objects.create(codigo="A-1", nombre="A1", activa=True)
        self.oficina2 = Oficina.objects.create(codigo="A-2", nombre="A2", activa=True)

        resp = self.client.post(
            "/auth/token/",
            {"username": "admin1", "password": "admin12345"},
            format="json",
        )
        self.assertEqual(resp.status_code, 200)
        self.token = resp.data["access"]
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {self.token}")

    def test_users_list_requires_admin(self):
        client = APIClient()
        resp_token = client.post(
            "/auth/token/",
            {"username": "user2", "password": "user12345"},
            format="json",
        )
        self.assertEqual(resp_token.status_code, 200)
        token = resp_token.data["access"]
        client.credentials(HTTP_AUTHORIZATION=f"Bearer {token}")

        resp = client.get("/usuarios/users/")
        self.assertEqual(resp.status_code, 403)

    def test_users_list_ok_for_admin(self):
        resp = self.client.get("/usuarios/users/")
        self.assertEqual(resp.status_code, 200)

    def test_users_create_ok_for_admin_and_creates_profile(self):
        resp = self.client.post(
            "/usuarios/users/",
            {
                "username": "newuser",
                "email": "newuser@example.com",
                "first_name": "Nuevo",
                "last_name": "Usuario",
                "is_active": True,
                "is_staff": False,
                "password": "Newuser12345!",
            },
            format="json",
        )
        self.assertEqual(resp.status_code, 201)

        user = User.objects.get(username="newuser")
        self.assertTrue(Profile.objects.filter(user=user).exists())

    def test_users_create_rejects_passwords_without_required_complexity(self):
        invalid_passwords = {
            "minimum length": "Aa1!aaa",
            "lowercase": "PASSWORD1!",
            "uppercase": "password1!",
            "number": "Password!",
            "special character": "Password1",
        }

        for case, password in invalid_passwords.items():
            with self.subTest(case=case):
                resp = self.client.post(
                    "/usuarios/users/",
                    {
                        "username": f"invalid_{case.replace(' ', '_')}",
                        "email": f"invalid_{case.replace(' ', '_')}@example.com",
                        "password": password,
                    },
                    format="json",
                )

                self.assertEqual(resp.status_code, 422)
                self.assertIn("password", resp.data.get("errors", {}))

    def test_users_create_can_assign_oficinas(self):
        resp = self.client.post(
            "/usuarios/users/",
            {
                "username": "newuser_offices",
                "email": "newuser_offices@example.com",
                "first_name": "Nuevo",
                "last_name": "Usuario",
                "is_active": True,
                "is_staff": False,
                "password": "Newuser12345!",
                "oficinas": [self.oficina1.id, self.oficina2.id],
            },
            format="json",
        )
        self.assertEqual(resp.status_code, 201)

        user = User.objects.get(username="newuser_offices")
        profile = Profile.objects.get(user=user)
        ids = sorted(list(profile.oficinas.values_list("id", flat=True)))
        self.assertEqual(ids, sorted([self.oficina1.id, self.oficina2.id]))

    def test_users_patch_can_update_oficinas(self):
        resp = self.client.patch(
            f"/usuarios/users/{self.user.id}/",
            {"oficinas": [self.oficina1.id]},
            format="json",
        )
        self.assertEqual(resp.status_code, 200)

        profile = Profile.objects.get(user=self.user)
        ids = list(profile.oficinas.values_list("id", flat=True))
        self.assertEqual(sorted(ids), [self.oficina1.id])

    def test_users_create_can_assign_roles(self):
        from django.contrib.auth.models import Group

        operador = Group.objects.create(name="Operador")

        resp = self.client.post(
            "/usuarios/users/",
            {
                "username": "newuser_roles",
                "email": "newuser_roles@example.com",
                "first_name": "Nuevo",
                "last_name": "Usuario",
                "is_active": True,
                "is_staff": True,
                "password": "Newuser12345!",
                "groups": [operador.name],
            },
            format="json",
        )
        self.assertEqual(resp.status_code, 201)

        user = User.objects.get(username="newuser_roles")
        self.assertEqual(list(user.groups.values_list("name", flat=True)), ["Operador"])

    def test_users_patch_can_update_roles(self):
        from django.contrib.auth.models import Group

        operador = Group.objects.create(name="Operador")

        resp = self.client.patch(
            f"/usuarios/users/{self.user.id}/",
            {"groups": [operador.name]},
            format="json",
        )
        self.assertEqual(resp.status_code, 200)

        self.user.refresh_from_db()
        self.assertEqual(list(self.user.groups.values_list("name", flat=True)), ["Operador"])

    def test_users_patch_prevents_removing_last_admin(self):
        resp = self.client.patch(
            f"/usuarios/users/{self.admin.id}/",
            {"groups": []},
            format="json",
        )
        self.assertEqual(resp.status_code, 422)

    def test_groups_list_ok_for_admin(self):
        resp = self.client.get("/usuarios/groups/")
        self.assertEqual(resp.status_code, 200)

    def test_set_roles_assigns_groups(self):
        from django.contrib.auth.models import Group

        operador = Group.objects.create(name="Operador")

        resp = self.client.post(
            f"/usuarios/users/{self.user.id}/set-roles/",
            {"groups": [operador.name]},
            format="json",
        )
        self.assertEqual(resp.status_code, 200)

        self.user.refresh_from_db()
        self.assertEqual(list(self.user.groups.values_list("name", flat=True)), ["Operador"])

    def test_set_oficinas_assigns_offices(self):
        resp = self.client.post(
            f"/usuarios/users/{self.user.id}/set-oficinas/",
            {"oficinas": [self.oficina1.id, self.oficina2.id]},
            format="json",
        )
        self.assertEqual(resp.status_code, 200)

        self.user.refresh_from_db()
        profile = Profile.objects.get(user=self.user)
        ids = sorted(list(profile.oficinas.values_list("id", flat=True)))
        self.assertEqual(ids, sorted([self.oficina1.id, self.oficina2.id]))

    def test_set_roles_prevents_removing_last_admin(self):
        resp = self.client.post(
            f"/usuarios/users/{self.admin.id}/set-roles/",
            {"groups": []},
            format="json",
        )
        self.assertEqual(resp.status_code, 400)
        self.assertIn("detail", resp.data)


class BootstrapRolesTests(APITestCase):
    def test_calidad_gets_existing_edit_permissions_only_for_accessible_content_types(self):
        calidad_group = Group.objects.create(name="Calidad")
        view_oficina = Permission.objects.get(
            codename="view_oficina", content_type__app_label="catalogos"
        )
        change_oficina = Permission.objects.get(
            codename="change_oficina", content_type=view_oficina.content_type
        )
        change_productor = Permission.objects.get(
            codename="change_productor", content_type__app_label="catalogos"
        )
        calidad_group.permissions.add(view_oficina)

        call_command("bootstrap_roles")

        permisos_calidad = calidad_group.permissions.all()
        self.assertIn(view_oficina, permisos_calidad)
        self.assertIn(change_oficina, permisos_calidad)
        self.assertNotIn(change_productor, permisos_calidad)

    def test_bootstrap_alinea_roles_minimos_con_matriz(self):
        call_command("bootstrap_roles")

        tecnico = Group.objects.get(name="Técnicos")
        self.assertTrue(tecnico.permissions.filter(codename="view_boleta", content_type__app_label="boletas").exists())
        self.assertTrue(tecnico.permissions.filter(codename="create", content_type__app_label="recepciones").exists())
        self.assertTrue(tecnico.permissions.filter(codename="add_traspaso", content_type__app_label="traspasos").exists())

        operador_financiero = Group.objects.get(name="Operador Financiero")
        self.assertTrue(operador_financiero.permissions.filter(codename="generate", content_type__app_label="boletas").exists())
        self.assertTrue(operador_financiero.permissions.filter(codename="view_recepcion", content_type__app_label="recepciones").exists())

        supervisor_molino = Group.objects.get(name="Supervisor Molino")
        for codename in ("validate", "reopen", "resolve_dispute"):
            self.assertTrue(
                supervisor_molino.permissions.filter(
                    codename=codename, content_type__app_label="recepciones"
                ).exists()
            )

        recepciones = Group.objects.get(name="Recepciones")
        self.assertFalse(
            recepciones.permissions.filter(
                codename="delete", content_type__app_label="recepciones"
            ).exists()
        )


class OfficeAccessUtilsTests(APITestCase):
    def test_extract_office_id_denies_unassigned_office_for_normal_user(self):
        user = User.objects.create_user(username="u1", email="u1@example.com", password="pass12345")
        Profile.objects.get_or_create(user=user)
        oficina = Oficina.objects.create(codigo="X-1", nombre="X1", activa=True)

        req = SimpleNamespace(
            headers={"X-Office-Id": str(oficina.id)},
            META={},
            query_params={},
            office_id=None,
            user=user,
        )

        with self.assertRaises(PermissionDenied):
            extract_office_id(req)
