import io
from decimal import Decimal

try:
    import openpyxl
except ImportError:  # pragma: no cover
    openpyxl = None

from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, Permission
from rest_framework.test import APIClient, APITestCase

from apps.catalogos.models import Contrato, ContratoVariedad, Oficina, Parametro, Productor, Variedad, Zafra
from apps.usuarios.permissions import ADMIN_GROUP_NAME


User = get_user_model()


class CatalogosPermissionsTests(APITestCase):
    def setUp(self):
        self.admin_group = Group.objects.create(name=ADMIN_GROUP_NAME)

        self.oficina = Oficina.objects.create(codigo="OF-1", nombre="Oficina 1", activa=True)

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

        self.user = User.objects.create_user(
            username="user",
            email="user@example.com",
            password="user12345",
        )
        self.user.profile.oficinas.add(self.oficina)

        self.editor = User.objects.create_user(
            username="editor",
            email="editor@example.com",
            password="editor12345",
        )
        perm = Permission.objects.get(codename="edit", content_type__app_label="catalogos")
        self.editor.user_permissions.add(perm)
        self.editor.profile.oficinas.add(self.oficina)

    def _token(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_catalogos_requires_auth(self):
        resp = self.client.get("/catalogos/zafras/")
        self.assertEqual(resp.status_code, 401)

    def test_catalogos_read_allowed_for_authenticated_user(self):
        token = self._token("user", "user12345")
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token}", HTTP_X_OFFICE_ID=str(self.oficina.id))

        resp = self.client.get("/catalogos/zafras/")
        self.assertEqual(resp.status_code, 200)

    def test_catalogos_write_forbidden_for_normal_user(self):
        token = self._token("user", "user12345")
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token}", HTTP_X_OFFICE_ID=str(self.oficina.id))

        resp = self.client.post(
            "/catalogos/variedades/",
            {"nombre": "V-test"},
            format="json",
        )
        self.assertEqual(resp.status_code, 403)

    def test_catalogos_write_allowed_for_admin(self):
        token = self._token("admin", "admin12345")
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token}", HTTP_X_OFFICE_ID=str(self.oficina.id))

        resp = self.client.post(
            "/catalogos/variedades/",
            {"nombre": "V-admin"},
            format="json",
        )
        self.assertEqual(resp.status_code, 201)

    def test_catalogos_write_allowed_for_user_with_permission(self):
        token = self._token("editor", "editor12345")
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token}", HTTP_X_OFFICE_ID=str(self.oficina.id))

        resp = self.client.post(
            "/catalogos/variedades/",
            {"nombre": "V-editor"},
            format="json",
        )
        self.assertEqual(resp.status_code, 201)


class CalidadCatalogosPermissionsTests(APITestCase):
    """El rol 'Calidad' solo debe poder gestionar Parámetros y Castigos."""

    def setUp(self):
        self.calidad_group = Group.objects.create(name="Calidad")
        perm = Permission.objects.get(
            codename="manage_calidad_catalogos", content_type__app_label="catalogos"
        )
        self.calidad_group.permissions.add(perm)

        self.calidad_user = User.objects.create_user(
            username="calidad",
            email="calidad@example.com",
            password="calidad12345",
        )
        self.calidad_user.groups.add(self.calidad_group)

        self.oficina = Oficina.objects.create(codigo="OF-CAL", nombre="Oficina Calidad", activa=True)
        self.calidad_user.profile.oficinas.add(self.oficina)

        self.parametro = Parametro.objects.create(nombre="Humedad", unidad="%")

    def _token(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_calidad_can_manage_parametros(self):
        token = self._token("calidad", "calidad12345")
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token}")

        resp = self.client.post(
            "/catalogos/parametros/",
            {"nombre": "Impurezas", "unidad": "%"},
            format="json",
        )
        self.assertEqual(resp.status_code, 201)

    def test_calidad_can_manage_castigos(self):
        token = self._token("calidad", "calidad12345")
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token}")

        resp = self.client.post(
            "/catalogos/castigos/",
            {
                "parametro": self.parametro.id,
                "limite_superior": "12.000",
                "descontar_kg_por_tonelada": "2.00",
                "bonificar_por_tonelada": "0.00",
                "es_rechazo": False,
            },
            format="json",
        )
        self.assertEqual(resp.status_code, 201)

    def test_calidad_cannot_manage_other_catalogos(self):
        token = self._token("calidad", "calidad12345")
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token}", HTTP_X_OFFICE_ID=str(self.oficina.id))

        resp = self.client.post(
            "/catalogos/variedades/",
            {"nombre": "V-calidad"},
            format="json",
        )
        self.assertEqual(resp.status_code, 403)


class CatalogosValidationsTests(APITestCase):
    def setUp(self):
        self.admin = User.objects.create_superuser(
            username="admin",
            email="admin@example.com",
            password="admin12345",
        )

        self.oficina = Oficina.objects.create(codigo="OF-1", nombre="Oficina 1", activa=True)

        resp = self.client.post(
            "/auth/token/",
            {"username": "admin", "password": "admin12345"},
            format="json",
        )
        self.token = resp.data["access"]
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {self.token}", HTTP_X_OFFICE_ID=str(self.oficina.id))

    def test_zafra_overlap_validation(self):
        resp1 = self.client.post(
            "/catalogos/zafras/",
            {"nombre": "Z1", "fecha_inicio": "2026-01-01", "fecha_fin": "2026-12-31", "activa": False},
            format="json",
        )
        self.assertEqual(resp1.status_code, 201)

        resp2 = self.client.post(
            "/catalogos/zafras/",
            {"nombre": "Z2", "fecha_inicio": "2026-06-01", "fecha_fin": "2026-06-30", "activa": False},
            format="json",
        )
        self.assertEqual(resp2.status_code, 422)

    def test_productor_rfc_validation(self):
        resp = self.client.post(
            "/catalogos/productores/",
            {"nombre": "Prod", "rfc": "BAD", "activo": True},
            format="json",
        )
        self.assertEqual(resp.status_code, 422)

    def test_contrato_accepts_peso_teorico_with_thousands_separator(self):
        zafra = Zafra.objects.create(
            nombre="Z 2026",
            fecha_inicio="2026-01-01",
            fecha_fin="2026-12-31",
            oficina=self.oficina,
        )
        variedad = Variedad.objects.create(nombre="V 2026", oficina=self.oficina)
        productor = Productor.objects.create(nombre="Prod", rfc="ABC123456T12", activo=True)

        resp = self.client.post(
            "/catalogos/contratos/",
            {
                "numero_contrato": "C-1",
                "productor": productor.id,
                "zafra": zafra.id,
                "variedades": [
                    {"variedad": variedad.id, "peso_teorico": "3,000.25"}
                ],
                "activo": True,
            },
            format="json",
        )
        self.assertEqual(resp.status_code, 201)
        contrato = Contrato.objects.get(pk=resp.data["id"])
        cv = contrato.variedades.first()
        self.assertIsNotNone(cv)
        self.assertEqual(cv.peso_teorico, Decimal("3000.250"))

    def test_search_productores_by_rfc(self):
        resp_ok = self.client.post(
            "/catalogos/productores/",
            {"nombre": "Prod 1", "rfc": "ABC123456T12", "activo": True},
            format="json",
        )
        self.assertEqual(resp_ok.status_code, 201)

        resp = self.client.get("/catalogos/productores/?search=ABC123456T12")
        self.assertEqual(resp.status_code, 200)
        data = resp.data
        if isinstance(data, list):
            self.assertGreaterEqual(len(data), 1)
        else:
            self.assertGreaterEqual(data.get("count", 0), 1)

    def test_productor_codigo_sica_se_puede_crear_y_editar(self):
        resp = self.client.post(
            "/catalogos/productores/",
            {"nombre": "Prod Sica", "rfc": "ABC123456T13", "codigo_sica": "SICA-001", "activo": True},
            format="json",
        )
        self.assertEqual(resp.status_code, 201)
        self.assertEqual(resp.data["codigo_sica"], "SICA-001")

        resp_edit = self.client.patch(
            f"/catalogos/productores/{resp.data['id']}/",
            {"codigo_sica": "SICA-002"},
            format="json",
        )
        self.assertEqual(resp_edit.status_code, 200)
        self.assertEqual(resp_edit.data["codigo_sica"], "SICA-002")

    def test_productor_codigo_sica_puede_quedar_en_blanco_para_varios_productores(self):
        resp1 = self.client.post(
            "/catalogos/productores/",
            {"nombre": "Prod Sin Sica 1", "rfc": "ABC123456T14", "activo": True},
            format="json",
        )
        self.assertEqual(resp1.status_code, 201)

        resp2 = self.client.post(
            "/catalogos/productores/",
            {"nombre": "Prod Sin Sica 2", "rfc": "ABC123456T15", "activo": True},
            format="json",
        )
        self.assertEqual(resp2.status_code, 201)

    def test_productor_codigo_sica_duplicado_es_rechazado(self):
        resp1 = self.client.post(
            "/catalogos/productores/",
            {"nombre": "Prod Sica A", "rfc": "ABC123456T16", "codigo_sica": "SICA-DUP", "activo": True},
            format="json",
        )
        self.assertEqual(resp1.status_code, 201)

        resp2 = self.client.post(
            "/catalogos/productores/",
            {"nombre": "Prod Sica B", "rfc": "ABC123456T17", "codigo_sica": "SICA-DUP", "activo": True},
            format="json",
        )
        self.assertEqual(resp2.status_code, 422)


class CatalogosFiltersSmokeTests(APITestCase):
    def setUp(self):
        self.admin = User.objects.create_superuser(
            username="admin",
            email="admin@example.com",
            password="admin12345",
        )

        self.oficina = Oficina.objects.create(codigo="OF-1", nombre="Oficina 1", activa=True)

        resp = self.client.post(
            "/auth/token/",
            {"username": "admin", "password": "admin12345"},
            format="json",
        )
        self.token = resp.data["access"]
        self.client.credentials(
            HTTP_AUTHORIZATION=f"Bearer {self.token}",
            HTTP_X_OFFICE_ID=str(self.oficina.id),
        )

    def test_filter_variedades_by_activa(self):
        self.client.post(
            "/catalogos/variedades/",
            {"nombre": "V1", "activa": True},
            format="json",
        )
        self.client.post(
            "/catalogos/variedades/",
            {"nombre": "V2", "activa": False},
            format="json",
        )

        resp = self.client.get("/catalogos/variedades/?activa=false")
        self.assertEqual(resp.status_code, 200)
        data = resp.data
        results = data if isinstance(data, list) else data.get("results", [])
        self.assertTrue(all(r.get("activa") is False for r in results))

    def test_filter_productores_con_contrato_activo(self):
        zafra = Zafra.objects.create(nombre="Z 2026", fecha_inicio="2026-01-01", fecha_fin="2026-12-31")
        variedad = Variedad.objects.create(nombre="V 2026")

        p1 = Productor.objects.create(nombre="Prod 1", rfc="ABC123456T12", activo=True)
        p2 = Productor.objects.create(nombre="Prod 2", rfc="DEF123456A34", activo=True)

        c1 = Contrato.objects.create(
            numero_contrato="C-1",
            productor=p1,
            zafra=zafra,
            activo=True,
        )
        ContratoVariedad.objects.create(contrato=c1, variedad=variedad)
        c2 = Contrato.objects.create(
            numero_contrato="C-2",
            productor=p2,
            zafra=zafra,
            activo=False,
        )
        ContratoVariedad.objects.create(contrato=c2, variedad=variedad)

        resp = self.client.get(f"/catalogos/productores/?con_contrato_activo=true&zafra_id={zafra.id}")
        self.assertEqual(resp.status_code, 200)
        data = resp.data
        results = data if isinstance(data, list) else data.get("results", [])
        ids = sorted([r.get("id") for r in results])
        self.assertEqual(ids, [p1.id])


class ContratoCargaMasivaTests(APITestCase):
    def setUp(self):
        self.admin = User.objects.create_superuser(
            username="admin",
            email="admin@example.com",
            password="admin12345",
        )
        self.oficina = Oficina.objects.create(codigo="OF-1", nombre="Oficina 1", activa=True)
        self.otra_oficina = Oficina.objects.create(codigo="OF-2", nombre="Oficina 2", activa=True)

        resp = self.client.post(
            "/auth/token/",
            {"username": "admin", "password": "admin12345"},
            format="json",
        )
        self.token = resp.data["access"]
        self.client.credentials(
            HTTP_AUTHORIZATION=f"Bearer {self.token}",
            HTTP_X_OFFICE_ID=str(self.oficina.id),
        )

        self.zafra = Zafra.objects.create(
            nombre="Zafra 2025",
            fecha_inicio="2025-01-01",
            fecha_fin="2025-12-31",
            oficina=self.oficina,
        )
        self.productor = Productor.objects.create(nombre="Productor A", rfc="ABCD010101ABC", activo=True)
        self.variedad1 = Variedad.objects.create(nombre="Trigo harinero", oficina=self.oficina)
        self.variedad2 = Variedad.objects.create(nombre="Trigo cristalino", oficina=self.oficina)

    def _build_excel(self, rows):
        wb = openpyxl.Workbook()
        ws = wb.active
        ws.title = "Contratos"
        ws.append(["numero_contrato", "productor_rfc", "zafra", "variedad", "peso_teorico", "activo"])
        for row in rows:
            ws.append(row)
        buffer = io.BytesIO()
        wb.save(buffer)
        buffer.seek(0)
        return buffer

    def test_descargar_template(self):
        if openpyxl is None:
            self.skipTest("openpyxl no instalado")

        resp = self.client.get("/catalogos/contratos/descargar-template/")
        self.assertEqual(resp.status_code, 200)
        self.assertIn("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", resp["Content-Type"])

    def test_cargar_excel_crea_contrato_con_multiples_variedades(self):
        if openpyxl is None:
            self.skipTest("openpyxl no instalado")

        rows = [
            ["C-2025-001", "ABCD010101ABC", "Zafra 2025", "Trigo harinero", "1500.500", "SI"],
            ["C-2025-001", "ABCD010101ABC", "Zafra 2025", "Trigo cristalino", "2000.000", "SI"],
        ]
        buffer = self._build_excel(rows)

        resp = self.client.post(
            "/catalogos/contratos/cargar-excel/",
            {"archivo": buffer},
            format="multipart",
        )
        self.assertEqual(resp.status_code, 200, resp.data)
        self.assertEqual(resp.data["contratos_creados"], 1)
        self.assertEqual(resp.data["contratos_actualizados"], 0)
        self.assertEqual(resp.data["total_variedades"], 2)

        contrato = Contrato.objects.get(numero_contrato="C-2025-001", zafra=self.zafra)
        self.assertEqual(contrato.oficina_id, self.oficina.id)
        self.assertEqual(contrato.variedades.count(), 2)
        self.assertEqual(
            Decimal(str(contrato.variedades.get(variedad=self.variedad1).peso_teorico)),
            Decimal("1500.500"),
        )
        self.assertEqual(
            Decimal(str(contrato.variedades.get(variedad=self.variedad2).peso_teorico)),
            Decimal("2000.000"),
        )

    def test_cargar_excel_actualiza_variedades_existentes(self):
        if openpyxl is None:
            self.skipTest("openpyxl no instalado")

        contrato = Contrato.objects.create(
            numero_contrato="C-2025-001",
            productor=self.productor,
            zafra=self.zafra,
            oficina=self.oficina,
            activo=True,
        )
        ContratoVariedad.objects.create(contrato=contrato, variedad=self.variedad1, peso_teorico=Decimal("1000.000"))

        rows = [
            ["C-2025-001", "ABCD010101ABC", "Zafra 2025", "Trigo cristalino", "2500.000", "SI"],
        ]
        buffer = self._build_excel(rows)

        resp = self.client.post(
            "/catalogos/contratos/cargar-excel/",
            {"archivo": buffer},
            format="multipart",
        )
        self.assertEqual(resp.status_code, 200, resp.data)
        self.assertEqual(resp.data["contratos_creados"], 0)
        self.assertEqual(resp.data["contratos_actualizados"], 1)
        self.assertEqual(resp.data["total_variedades"], 1)

        contrato.refresh_from_db()
        self.assertEqual(contrato.variedades.count(), 1)
        self.assertEqual(
            Decimal(str(contrato.variedades.first().peso_teorico)),
            Decimal("2500.000"),
        )

    def test_cargar_excel_rechaza_variedad_de_otra_oficina(self):
        if openpyxl is None:
            self.skipTest("openpyxl no instalado")

        Variedad.objects.create(nombre="Variedad externa", oficina=self.otra_oficina)

        rows = [
            ["C-2025-001", "ABCD010101ABC", "Zafra 2025", "Variedad externa", "1500.000", "SI"],
        ]
        buffer = self._build_excel(rows)

        resp = self.client.post(
            "/catalogos/contratos/cargar-excel/",
            {"archivo": buffer},
            format="multipart",
        )
        self.assertEqual(resp.status_code, 422)
        self.assertIn("no pertenece a la oficina indicada", str(resp.data))

    def test_cargar_excel_rechaza_variedad_duplicada(self):
        if openpyxl is None:
            self.skipTest("openpyxl no instalado")

        rows = [
            ["C-2025-001", "ABCD010101ABC", "Zafra 2025", "Trigo harinero", "1500.000", "SI"],
            ["C-2025-001", "ABCD010101ABC", "Zafra 2025", "Trigo harinero", "2000.000", "SI"],
        ]
        buffer = self._build_excel(rows)

        resp = self.client.post(
            "/catalogos/contratos/cargar-excel/",
            {"archivo": buffer},
            format="multipart",
        )
        self.assertEqual(resp.status_code, 422)
        self.assertIn("duplicada", str(resp.data))

    def test_cargar_excel_requiere_office_id(self):
        if openpyxl is None:
            self.skipTest("openpyxl no instalado")

        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {self.token}")
        rows = [
            ["C-2025-001", "ABCD010101ABC", "Zafra 2025", "Trigo harinero", "1500.000", "SI"],
        ]
        buffer = self._build_excel(rows)

        resp = self.client.post(
            "/catalogos/contratos/cargar-excel/",
            {"archivo": buffer},
            format="multipart",
        )
        self.assertEqual(resp.status_code, 422)
        self.assertIn("office_id", str(resp.data))
