from __future__ import annotations

import os
import shutil
import subprocess
import textwrap
import unittest
from pathlib import Path


class CasesJsRenderHelpersTest(unittest.TestCase):
    @staticmethod
    def _resolve_real_node_binary() -> str | None:
        candidates = [
            shutil.which("node"),
            shutil.which("nodejs"),
            "/usr/bin/node",
            "/usr/local/bin/node",
        ]
        for candidate in candidates:
            if not candidate:
                continue
            path = Path(candidate)
            if path.is_file() and path.exists():
                return str(path)
        return None

    def test_cases_render_helpers_classify_cups_and_extract_lists(self) -> None:
        node_bin = self._resolve_real_node_binary()
        if node_bin is None:
            self.skipTest("Real Node.js runtime not available in this environment")

        script_path = Path(__file__).resolve().parents[1] / "static" / "js" / "cases.js"
        node_script = textwrap.dedent(
            f"""
            const fs = require("fs");
            const vm = require("vm");

            const dateTimeSource = fs.readFileSync(
              {str(Path(__file__).resolve().parents[1] / "static" / "js" / "date-time.js")!r},
              "utf8"
            );
            const source = fs.readFileSync({str(script_path)!r}, "utf8");
            const sandbox = {{
              globalThis: null,
              document: {{ querySelector: () => null }},
              console,
            }};
            sandbox.globalThis = sandbox;
            vm.createContext(sandbox);
            vm.runInContext(dateTimeSource, sandbox);
            vm.runInContext(source, sandbox);

            const helpers = sandbox.__CASES_RENDER_HELPERS__;
            if (!helpers) {{
              throw new Error("helpers_not_exposed");
            }}

            const presentation = helpers.buildDocumentPresentationModel({{
              case_number: "178474",
              patient_id: "93387170",
              procedimientos_extraidos: ["Procedimiento A"],
              medicamentos_extraidos: [{{
                nombre: "Dipirona",
                posologia: "Intravenosa cada 8 horas",
                dosis: "2 gr",
                cantidad: 16,
                estados: ["administrado", "facturado"],
                codigo_referencia: "19993038-8",
              }}],
              codigos_cups: [
                {{
                  procedimiento: "Tomografía",
                  codigo_cups: "879523 - TOMOGRAFIA COMPUTADA DE MIEMBROS INFERIORES",
                }},
                {{
                  procedimiento: "Sala observación",
                  codigo_cups: "5DSA01 - OPCION A\\n5DSM01 - OPCION B",
                }},
                {{
                  procedimiento: "Ligamentos",
                  codigo_cups:
                    "No se encontró una coincidencia exacta para \\"Reparación de 2 o más ligamentos\\"",
                }},
              ],
            }});

            if (presentation.caseNumber !== "178474") {{
              throw new Error("missing_case_number");
            }}
            if (presentation.patientId !== "93387170") {{
              throw new Error("missing_patient_id");
            }}
            if (presentation.procedures.length !== 1 || presentation.procedures[0] !== "Procedimiento A") {{
              throw new Error("procedures_not_exposed");
            }}
            if (presentation.medications.length !== 1) {{
              throw new Error("structured_medication_should_render");
            }}
            const medication = presentation.medications[0];
            if (medication.name !== "Dipirona" || medication.posology !== "Intravenosa cada 8 horas") {{
              throw new Error("medication_identity_or_posology_lost");
            }}
            if (medication.dose !== "2 gr" || medication.quantity !== "16") {{
              throw new Error("medication_dose_or_quantity_lost");
            }}
            if (medication.statuses.join("|") !== "administrado|facturado") {{
              throw new Error("medication_statuses_lost");
            }}
            if (presentation.cupsEntries[0].code !== "879523" || presentation.cupsEntries[0].isReview) {{
              throw new Error("usable_cups_misclassified");
            }}
            if (presentation.cupsEntries[1].code !== "Pendiente revisión" || !presentation.cupsEntries[1].isReview) {{
              throw new Error("multiline_cups_misclassified");
            }}
            if (presentation.cupsEntries[2].code !== "Pendiente revisión" || !presentation.cupsEntries[2].isReview) {{
              throw new Error("message_cups_misclassified");
            }}
            """
        )

        completed = subprocess.run(
            [node_bin, "-e", node_script],
            check=False,
            capture_output=True,
            text=True,
        )

        if completed.returncode != 0:
            self.fail(completed.stderr or completed.stdout or "node test failed")

    def test_cases_render_helpers_build_sheet_sections_and_pick_preferred_document(self) -> None:
        node_bin = self._resolve_real_node_binary()
        if node_bin is None:
            self.skipTest("Real Node.js runtime not available in this environment")

        script_path = Path(__file__).resolve().parents[1] / "static" / "js" / "cases.js"
        node_script = textwrap.dedent(
            f"""
            const fs = require("fs");
            const vm = require("vm");

            const dateTimeSource = fs.readFileSync(
              {str(Path(__file__).resolve().parents[1] / "static" / "js" / "date-time.js")!r},
              "utf8"
            );
            const source = fs.readFileSync({str(script_path)!r}, "utf8");
            const sandbox = {{
              globalThis: null,
              document: {{ querySelector: () => null }},
              console,
            }};
            sandbox.globalThis = sandbox;
            vm.createContext(sandbox);
            vm.runInContext(dateTimeSource, sandbox);
            vm.runInContext(source, sandbox);

            const helpers = sandbox.__CASES_RENDER_HELPERS__;
            if (!helpers) {{
              throw new Error("helpers_not_exposed");
            }}

            const groups = [
              {{
                type: "radiologia",
                type_label: "Radiologia",
                documents: [
                  {{
                    document_id: "doc-rx",
                    name: "rayos-x.pdf",
                    type: "radiologia",
                    type_label: "Radiologia",
                    updated_at: "2026-04-28T09:00:00Z",
                  }},
                ],
              }},
              {{
                type: "factura",
                type_label: "Factura",
                documents: [
                  {{
                    document_id: "doc-factura",
                    name: "factura.pdf",
                    type: "factura",
                    type_label: "Factura",
                    updated_at: "2026-04-28T10:00:00Z",
                  }},
                ],
              }},
              {{
                type: "laboratorio",
                type_label: "Laboratorio",
                documents: [
                  {{
                    document_id: "doc-lab-z",
                    name: "zeta-lab.pdf",
                    type: "laboratorio",
                    type_label: "Laboratorio",
                    updated_at: "2026-04-28T08:00:00Z",
                  }},
                  {{
                    document_id: "doc-lab-a",
                    name: "alfa-lab.pdf",
                    type: "laboratorio",
                    type_label: "Laboratorio",
                    updated_at: "2026-04-28T08:00:00Z",
                  }},
                ],
              }},
              {{
                type: "historia_clinica",
                type_label: "Historia clinica",
                documents: [
                  {{
                    document_id: "doc-historia",
                    name: "historia.pdf",
                    type: "historia_clinica",
                    type_label: "Historia clinica",
                    updated_at: "2026-04-28T07:00:00Z",
                  }},
                ],
              }},
              {{
                type: "quirurgico",
                type_label: "Quirurgico",
                documents: [
                  {{
                    document_id: "doc-qx",
                    name: "quirurgico.pdf",
                    type: "quirurgico",
                    type_label: "Quirurgico",
                    updated_at: "2026-04-28T11:00:00Z",
                  }},
                ],
              }},
            ];

            const sections = helpers.buildSheetDocumentSections(groups);
            if (sections.length !== 3) {{
              throw new Error(`expected_three_sections:${{sections.length}}`);
            }}

            const titles = sections.map((section) => section.title).join("|");
            if (titles !== "Historia clínica|Factura|Otros documentos") {{
              throw new Error(`unexpected_section_order:${{titles}}`);
            }}

            const otherDocumentIds = sections[2].documents.map((document) => document.document_id).join("|");
            if (otherDocumentIds !== "doc-qx|doc-rx|doc-lab-a|doc-lab-z") {{
              throw new Error(`unexpected_other_documents_order:${{otherDocumentIds}}`);
            }}

            if (helpers.pickPreferredSheetDocumentId(groups, "") !== "doc-historia") {{
              throw new Error("history_should_be_default");
            }}
            if (helpers.pickPreferredSheetDocumentId(groups, "doc-factura") !== "doc-factura") {{
              throw new Error("preferred_existing_document_should_win");
            }}

            const noHistoryGroups = groups.filter((group) => group.type !== "historia_clinica");
            if (helpers.pickPreferredSheetDocumentId(noHistoryGroups, "") !== "doc-factura") {{
              throw new Error("invoice_should_be_default_when_history_missing");
            }}

            const othersOnlyGroups = groups.filter(
              (group) => group.type !== "historia_clinica" && group.type !== "factura"
            );
            if (helpers.pickPreferredSheetDocumentId(othersOnlyGroups, "") !== "doc-qx") {{
              throw new Error("other_documents_should_fallback_when_history_and_invoice_missing");
            }}
            """
        )

        completed = subprocess.run(
            [node_bin, "-e", node_script],
            check=False,
            capture_output=True,
            text=True,
        )

        if completed.returncode != 0:
            self.fail(completed.stderr or completed.stdout or "node test failed")

    def test_cases_render_helpers_format_machine_dates_for_invoice_rendering(self) -> None:
        node_bin = self._resolve_real_node_binary()
        if node_bin is None:
            self.skipTest("Real Node.js runtime not available in this environment")

        script_path = Path(__file__).resolve().parents[1] / "static" / "js" / "cases.js"
        node_script = textwrap.dedent(
            f"""
            const fs = require("fs");
            const vm = require("vm");

            const dateTimeSource = fs.readFileSync(
              {str(Path(__file__).resolve().parents[1] / "static" / "js" / "date-time.js")!r},
              "utf8"
            );
            const source = fs.readFileSync({str(script_path)!r}, "utf8");
            const sandbox = {{
              globalThis: null,
              document: {{ querySelector: () => null }},
              console,
            }};
            sandbox.globalThis = sandbox;
            vm.createContext(sandbox);
            vm.runInContext(dateTimeSource, sandbox);
            vm.runInContext(source, sandbox);

            const helpers = sandbox.__CASES_RENDER_HELPERS__;
            if (!helpers) {{
              throw new Error("helpers_not_exposed");
            }}

            const formattedDate = helpers.formatMachineDate("2024-06-26");
            const formattedDateTime = helpers.formatMachineDate("2024-05-27T16:30Z");
            const formattedBogotaDateTime = helpers.formatMachineDate("2024-05-27T11:30-05:00");
            const formattedLegacyDateTime = helpers.formatMachineDate("2024-05-27T11:30");
            const invalidDate = helpers.formatMachineDate("27 mayo, 2024");

            if (!formattedDate || !formattedDate.includes("2024")) {{
              throw new Error(`unexpected_date:${{formattedDate}}`);
            }}
            if (!formattedDateTime || !formattedDateTime.includes("2024")) {{
              throw new Error(`unexpected_datetime:${{formattedDateTime}}`);
            }}
            if (!formattedDateTime.includes("11:30")) {{
              throw new Error(`unexpected_bogota_time:${{formattedDateTime}}`);
            }}
            if (formattedBogotaDateTime !== formattedDateTime) {{
              throw new Error(`offsets_should_match:${{formattedBogotaDateTime}}:${{formattedDateTime}}`);
            }}
            if (formattedLegacyDateTime !== formattedDateTime) {{
              throw new Error(`legacy_should_assume_bogota:${{formattedLegacyDateTime}}:${{formattedDateTime}}`);
            }}
            if (invalidDate !== "") {{
              throw new Error(`invalid_should_be_empty:${{invalidDate}}`);
            }}
            """
        )

        completed = subprocess.run(
            [node_bin, "-e", node_script],
            check=False,
            capture_output=True,
            text=True,
            env={**os.environ, "TZ": "Pacific/Auckland"},
        )

        if completed.returncode != 0:
            self.fail(completed.stderr or completed.stdout or "node test failed")


if __name__ == "__main__":
    unittest.main()
