"""Tests for JSON-to-tree, JSON-to-summary, and JSON-to-CSV export utilities."""

import pytest
from mld_sdk.export import auto_json_to_tree, auto_json_to_csv, auto_json_to_summary


class TestAutoJsonToTree:
    def test_flat_dict_becomes_folder_with_leaves(self):
        data = {"name": "Plate 1", "wells": 96, "active": True}
        nodes = auto_json_to_tree(data)
        assert len(nodes) == 3
        assert nodes[0]["label"] == "name: Plate 1"
        assert nodes[0]["type"] == "custom"

    def test_nested_dict_becomes_folder(self):
        data = {"config": {"format": 96, "label": "Plate 1"}}
        nodes = auto_json_to_tree(data)
        assert len(nodes) == 1
        assert nodes[0]["label"] == "config"
        assert nodes[0]["type"] == "folder"
        assert len(nodes[0]["children"]) == 2

    def test_list_becomes_numbered_children(self):
        data = {"compounds": ["Aspirin", "Ibuprofen"]}
        nodes = auto_json_to_tree(data)
        assert nodes[0]["label"] == "compounds"
        assert len(nodes[0]["children"]) == 2
        assert nodes[0]["children"][0]["label"] == "[0]: Aspirin"

    def test_empty_dict_returns_empty(self):
        assert auto_json_to_tree({}) == []

    def test_biology_type_detection(self):
        """Keys like 'plates', 'samples', 'treatments' get biology node types."""
        data = {"plates": [{"id": "p1"}], "samples": [{"id": "s1"}]}
        nodes = auto_json_to_tree(data)
        assert nodes[0]["type"] == "plate"
        assert nodes[1]["type"] == "sample"


class TestAutoJsonToTreeCompact:
    """Tests for compact=True mode that reduces node count."""

    def test_compact_filters_null_values(self):
        data = {"name": "Plate 1", "notes": None, "active": True}
        nodes = auto_json_to_tree(data, compact=True)
        labels = [n["label"] for n in nodes]
        assert "name: Plate 1" in labels
        assert "active: True" in labels
        assert not any("notes" in l for l in labels)

    def test_compact_filters_empty_strings(self):
        data = {"name": "Plate 1", "description": "", "wells": 96}
        nodes = auto_json_to_tree(data, compact=True)
        assert len(nodes) == 2
        labels = [n["label"] for n in nodes]
        assert not any("description" in l for l in labels)

    def test_compact_filters_empty_lists(self):
        data = {"name": "Plate 1", "compounds": []}
        nodes = auto_json_to_tree(data, compact=True)
        assert len(nodes) == 1
        assert nodes[0]["label"] == "name: Plate 1"

    def test_compact_collapses_scalar_dict(self):
        data = {"config": {"format": 96, "label": "P1"}}
        nodes = auto_json_to_tree(data, compact=True)
        assert len(nodes) == 1
        assert "format: 96" in nodes[0]["label"]
        assert "label: P1" in nodes[0]["label"]
        assert "children" not in nodes[0]

    def test_compact_collapses_list_items(self):
        data = {"samples": [
            {"name": "S1", "well_position": "A1", "sample_type": "unknown"},
            {"name": "S2", "well_position": "A2", "sample_type": "standard"},
        ]}
        nodes = auto_json_to_tree(data, compact=True)
        # The list node should exist
        assert nodes[0]["label"] == "samples"
        children = nodes[0]["children"]
        assert len(children) == 2
        # Each child should be collapsed: label includes scalar values
        assert "S1" in children[0]["label"]
        assert "well_position: A1" in children[0]["label"]
        # "name" is a label key, so it should be in the label but not repeated in summary
        assert "children" not in children[0]

    def test_compact_nested_plate_with_samples(self):
        """Plate with nested samples: no redundant 'name' child, no badge on plate item."""
        data = {
            "cell_line": None,
            "lcms_method": None,
            "schema_version": "1.0",
            "extraction_results": [],
            "plates": [
                {
                    "name": "exp001_plate1",
                    "layout_type": "54-vial",
                    "samples": [
                        {"name": "S1", "well": "A1"},
                        {"name": "S2", "well": "A2"},
                    ]
                }
            ]
        }
        nodes = auto_json_to_tree(data, compact=True)
        labels = [n["label"] for n in nodes]
        # Null scalars should be filtered
        assert not any("cell_line" in l for l in labels)
        assert not any("lcms_method" in l for l in labels)
        # Empty list should be filtered
        assert not any("extraction_results" in l for l in labels)
        # schema_version is a scalar, should remain
        assert any("schema_version: 1.0" in l for l in labels)
        # plates node should exist with children
        plates_node = next(n for n in nodes if n["label"] == "plates")
        plate_item = plates_node["children"][0]
        assert plate_item["label"] == "exp001_plate1"
        # Plate item should have children (layout_type + samples)
        child_labels = [c["label"] for c in plate_item["children"]]
        # layout_type as a scalar prop, "name" should NOT appear (it's the label key)
        assert any("layout_type: 54-vial" in l for l in child_labels)
        assert not any(l.startswith("name:") for l in child_labels)
        # samples sub-list should be present
        assert any("samples" in l for l in child_labels)

    def test_compact_produces_fewer_nodes(self):
        """Compact mode should produce significantly fewer nodes than default."""
        data = {
            "cell_line": "HeLa",
            "lcms_method": "Standard",
            "notes": None,
            "plates": [
                {
                    "name": "Plate 1",
                    "samples": [
                        {"name": f"S{i}", "well": f"A{i}", "type": "unknown"}
                        for i in range(10)
                    ]
                }
            ]
        }
        default_nodes = auto_json_to_tree(data)
        compact_nodes = auto_json_to_tree(data, compact=True)

        def count_nodes(nodes):
            total = len(nodes)
            for n in nodes:
                if "children" in n:
                    total += count_nodes(n["children"])
            return total

        assert count_nodes(compact_nodes) < count_nodes(default_nodes)


class TestAutoJsonToSummary:
    """Tests for auto_json_to_summary() structured summary output."""

    SAMPLE_DATA = {
        "cell_line": "HeLa",
        "lcms_method": "Targeted Metabolomics",
        "notes": None,
        "plates": [
            {
                "name": "exp001_plate1",
                "format": 96,
                "samples": [
                    {"well_position": "A1", "sample_name": "S1", "sample_type": "unknown"},
                    {"well_position": "A2", "sample_name": "S2", "sample_type": "standard"},
                    {"well_position": "A3", "sample_name": "S3", "sample_type": None},
                ]
            }
        ]
    }

    def test_metadata_extracts_scalars(self):
        result = auto_json_to_summary(self.SAMPLE_DATA)
        assert result["metadata"]["cell_line"] == "HeLa"
        assert result["metadata"]["lcms_method"] == "Targeted Metabolomics"

    def test_metadata_filters_nulls(self):
        result = auto_json_to_summary(self.SAMPLE_DATA)
        assert "notes" not in result["metadata"]

    def test_group_section_detected(self):
        result = auto_json_to_summary(self.SAMPLE_DATA)
        assert len(result["sections"]) == 1
        section = result["sections"][0]
        assert section["key"] == "plates"
        assert section["type"] == "group"
        assert len(section["items"]) == 1

    def test_group_item_has_embedded_table(self):
        result = auto_json_to_summary(self.SAMPLE_DATA)
        item = result["sections"][0]["items"][0]
        assert item["label"] == "exp001_plate1"
        assert item["item_count"] == 3
        assert item["item_key"] == "samples"
        assert "well_position" in item["columns"]
        assert "sample_name" in item["columns"]
        assert len(item["rows"]) == 3

    def test_object_columns_filtered(self):
        """Columns containing objects (dicts/lists) should be excluded."""
        data = {
            "items": [
                {"name": "A", "value": 1, "tags": {"env": "prod"}},
                {"name": "B", "value": 2, "tags": {"env": "dev"}},
            ]
        }
        result = auto_json_to_summary(data)
        section = result["sections"][0]
        assert "tags" not in section["columns"]
        assert "name" in section["columns"]
        assert "value" in section["columns"]

    def test_null_columns_filtered(self):
        """Columns that are entirely null should be excluded."""
        data = {
            "items": [
                {"name": "A", "value": 1, "notes": None},
                {"name": "B", "value": 2, "notes": None},
            ]
        }
        result = auto_json_to_summary(data)
        section = result["sections"][0]
        assert "notes" not in section["columns"]

    def test_flat_table_section(self):
        """A list of dicts without nested lists becomes a table section."""
        data = {
            "reagents": [
                {"name": "DMEM", "conc": 1.0},
                {"name": "FBS", "conc": 0.1},
            ]
        }
        result = auto_json_to_summary(data)
        section = result["sections"][0]
        assert section["type"] == "table"
        assert section["row_count"] == 2
        assert "name" in section["columns"]

    def test_nested_dict_flattens_to_metadata(self):
        data = {"config": {"format": 96, "method": "LC-MS"}, "name": "Test"}
        result = auto_json_to_summary(data)
        assert result["metadata"]["config.format"] == 96
        assert result["metadata"]["config.method"] == "LC-MS"
        assert result["metadata"]["name"] == "Test"

    def test_empty_data(self):
        result = auto_json_to_summary({})
        assert result["metadata"] == {}
        assert result["sections"] == []

    def test_group_item_metadata_excludes_lists(self):
        result = auto_json_to_summary(self.SAMPLE_DATA)
        item = result["sections"][0]["items"][0]
        # The group item metadata should contain scalar fields only
        assert "format" in item["metadata"]
        assert "samples" not in item["metadata"]

    def test_multiple_plates(self):
        data = {
            "plates": [
                {"name": "P1", "samples": [{"well": "A1"}]},
                {"name": "P2", "samples": [{"well": "B1"}, {"well": "B2"}]},
            ]
        }
        result = auto_json_to_summary(data)
        items = result["sections"][0]["items"]
        assert len(items) == 2
        assert items[0]["item_count"] == 1
        assert items[1]["item_count"] == 2


class TestAutoJsonToCsv:
    def test_flat_dict_to_csv(self):
        data = {"name": "Plate 1", "wells": 96}
        csv_str = auto_json_to_csv(data)
        lines = csv_str.strip().splitlines()
        assert lines[0].strip() == "key,value"
        assert "name,Plate 1" in lines[1]

    def test_nested_dict_flattens_with_dot_notation(self):
        data = {"plate": {"format": 96, "label": "P1"}}
        csv = auto_json_to_csv(data)
        assert "plate.format" in csv
        assert "plate.label" in csv

    def test_list_of_dicts_to_table(self):
        data = {"compounds": [
            {"name": "Aspirin", "conc": 10},
            {"name": "Ibuprofen", "conc": 5},
        ]}
        csv = auto_json_to_csv(data)
        assert "name,conc" in csv
        assert "Aspirin" in csv

    def test_empty_dict_returns_header_only(self):
        csv = auto_json_to_csv({})
        assert csv.strip() == "key,value"
