from __future__ import annotations

from pathlib import Path

import pytest

from excelminer.backends.base import AnalysisContext, AnalysisOptions
from excelminer.backends.openpyxl_backend import OpenpyxlBackend
from excelminer.model.entities import PivotTable
from excelminer.model.graph import WorkbookGraph


def _write_formula_workbook(path: Path) -> None:
    try:
        from openpyxl import Workbook
    except Exception as e:  # noqa: BLE001
        pytest.skip(f"openpyxl missing in test env: {e}")

    wb = Workbook()
    ws = wb.active
    ws.title = "Sheet1"
    ws["A1"] = "=1+1"
    ws["B2"] = "=A1*2"
    ws["D4"] = "=B2*2"
    wb.save(path)


def _write_multi_sheet_workbook(path: Path) -> None:
    try:
        from openpyxl import Workbook
    except Exception as e:  # noqa: BLE001
        pytest.skip(f"openpyxl missing in test env: {e}")

    wb = Workbook()
    ws1 = wb.active
    ws1.title = "First"
    ws1["A1"] = "=1+1"
    ws2 = wb.create_sheet("Second")
    ws2["A1"] = "=2+2"
    wb.save(path)


def _write_pivot_formula_workbook(path: Path) -> None:
    try:
        from openpyxl import Workbook
    except Exception as e:  # noqa: BLE001
        pytest.skip(f"openpyxl missing in test env: {e}")

    wb = Workbook()
    ws = wb.active
    ws.title = "Sheet1"
    ws["A1"] = "=1+1"
    ws["C3"] = "=2+2"
    wb.save(path)


def _write_xml_mode_workbook(path: Path) -> None:
    try:
        from openpyxl import Workbook
    except Exception as e:  # noqa: BLE001
        pytest.skip(f"openpyxl missing in test env: {e}")

    wb = Workbook()
    ws = wb.active
    ws.title = "Sheet1"
    ws["A1"] = 123
    ws["B2"] = "=A1+1"
    wb.save(path)


def test_analysis_options_rejects_sheet_names_and_indexes() -> None:
    with pytest.raises(ValueError):
        AnalysisOptions(formula_sheet_names=["Sheet1"], formula_sheet_indexes=[1])


def test_openpyxl_backend_formula_chunking(tmp_path: Path) -> None:
    path = tmp_path / "formulas.xlsx"
    _write_formula_workbook(path)

    options = AnalysisOptions(
        include_formulas=True,
        row_chunk_size=1,
        column_chunk_size=1,
        max_rows_per_sheet=3,
        max_cols_per_sheet=3,
    )
    ctx = AnalysisContext(path=path, options=options)
    graph = WorkbookGraph()

    backend = OpenpyxlBackend()
    report = backend.extract(ctx, graph)

    assert report.backend == "openpyxl"
    assert report.stats["formula_cells"] == 2

    sheet_node = graph.get_by_key("sheet", "Sheet1")
    assert sheet_node is not None

    a1 = graph.get_by_key("formula_cell", "Sheet1!A1")
    b2 = graph.get_by_key("formula_cell", "Sheet1!B2")
    d4 = graph.get_by_key("formula_cell", "Sheet1!D4")

    assert a1 is not None
    assert b2 is not None
    assert d4 is None

    contains_edges = [
        edge
        for edge in graph.edges
        if edge.kind == "contains" and edge.src == sheet_node.id
    ]
    assert {edge.dst for edge in contains_edges} == {a1.id, b2.id}


def test_openpyxl_backend_respects_formula_sheet_names(tmp_path: Path) -> None:
    path = tmp_path / "multi.xlsx"
    _write_multi_sheet_workbook(path)

    options = AnalysisOptions(include_formulas=True, formula_sheet_names=["Second"])
    ctx = AnalysisContext(path=path, options=options)
    graph = WorkbookGraph()

    report = OpenpyxlBackend().extract(ctx, graph)

    assert report.stats["formula_cells"] == 1
    assert graph.get_by_key("formula_cell", "Second!A1") is not None
    assert graph.get_by_key("formula_cell", "First!A1") is None


def test_openpyxl_backend_respects_formula_sheet_indexes(tmp_path: Path) -> None:
    path = tmp_path / "multi.xlsx"
    _write_multi_sheet_workbook(path)

    options = AnalysisOptions(include_formulas=True, formula_sheet_indexes=[1])
    ctx = AnalysisContext(path=path, options=options)
    graph = WorkbookGraph()

    report = OpenpyxlBackend().extract(ctx, graph)

    assert report.stats["formula_cells"] == 1
    assert graph.get_by_key("formula_cell", "First!A1") is not None
    assert graph.get_by_key("formula_cell", "Second!A1") is None


def test_openpyxl_backend_skips_pivot_ranges(tmp_path: Path) -> None:
    path = tmp_path / "pivot_formulas.xlsx"
    _write_pivot_formula_workbook(path)

    graph = WorkbookGraph()
    graph.upsert(
        PivotTable.make(
            key="Sheet1|Pivot1|1",
            id="pivot:Sheet1|Pivot1|1",
            name="Pivot1",
            sheet_name="Sheet1",
            extra={"pivot_ref": "A1:B2"},
        )
    )

    options = AnalysisOptions(include_formulas=True)
    ctx = AnalysisContext(path=path, options=options)

    report = OpenpyxlBackend().extract(ctx, graph)

    assert report.stats["formula_cells"] == 1
    assert graph.get_by_key("formula_cell", "Sheet1!A1") is None
    assert graph.get_by_key("formula_cell", "Sheet1!C3") is not None


def test_openpyxl_backend_xml_mode_uses_formula_cells(tmp_path: Path) -> None:
    path = tmp_path / "xml_mode.xlsx"
    _write_xml_mode_workbook(path)

    options = AnalysisOptions(
        include_formulas=True,
        formula_scan_mode="xml",
        max_cells_per_sheet=1,
    )
    ctx = AnalysisContext(path=path, options=options)
    graph = WorkbookGraph()

    report = OpenpyxlBackend().extract(ctx, graph)

    assert report.stats["formula_cells"] == 1
    assert graph.get_by_key("formula_cell", "Sheet1!B2") is not None
