"""Comprehensive CLI coverage tests for uncovered handlers.

Tests: credit pd, credit ontology JSON/export, credit fairness JSON,
discover JSON, simulate, fabric ingest/profile, fabric quality JSON,
providers cost/check JSON/list JSON, cost status/set, tenant CRUD,
serve/edit (mocked), model load JSON/versions JSON, main dispatch edges.
"""

from __future__ import annotations

import json
import os
import tempfile
from pathlib import Path
from unittest.mock import patch

import pytest

from orxaq.experiences.cli import main

# ---------------------------------------------------------------------------
# Credit PD subcommand (zero coverage before)
# ---------------------------------------------------------------------------


class TestCreditPD:
    def test_pd_text_output(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["credit", "pd"])
        assert rc == 0
        out = capsys.readouterr().out
        assert "PD" in out

    def test_pd_json_output(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "credit", "pd"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert "avg_pd" in data
        assert data["sample_size"] > 0

    def test_pd_borrower_text(self, capsys: pytest.CaptureFixture) -> None:
        borrower = json.dumps({"income": 60000, "credit_score": 700, "dti": 0.3})
        rc = main(["credit", "pd", "--borrower", borrower])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Predicted PD" in out

    def test_pd_borrower_json(self, capsys: pytest.CaptureFixture) -> None:
        borrower = json.dumps({"income": 60000, "credit_score": 700, "dti": 0.3})
        rc = main(["--json", "credit", "pd", "--borrower", borrower])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert "pd" in data
        assert isinstance(data["pd"], float)

    def test_pd_with_scenario_text(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["credit", "pd", "--scenario", "adverse"])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Stress test" in out
        assert "Base PD" in out
        assert "Stress PD" in out

    def test_pd_borrower_with_scenario(self, capsys: pytest.CaptureFixture) -> None:
        """Borrower text mode falls through to scenario block."""
        borrower = json.dumps({"income": 60000, "credit_score": 700, "dti": 0.3})
        rc = main(["credit", "pd", "--borrower", borrower, "--scenario", "adverse"])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Predicted PD" in out
        assert "Stress test" in out


# ---------------------------------------------------------------------------
# Credit ontology: JSON + export paths
# ---------------------------------------------------------------------------


class TestCreditOntologyFull:
    def test_ontology_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "credit", "ontology"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert "entities" in data
        assert "edges" in data
        assert "entity_count" in data
        assert data["entity_count"] > 0

    def test_ontology_export_file(self, capsys: pytest.CaptureFixture) -> None:
        with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
            outpath = f.name
        try:
            rc = main(["credit", "ontology", "--export", outpath])
            assert rc == 0
            out = capsys.readouterr().out
            assert "Exported" in out
            with open(outpath) as f:
                data = json.load(f)
            assert "variables" in data
            assert data["count"] > 0
        finally:
            os.unlink(outpath)


# ---------------------------------------------------------------------------
# Credit fairness: JSON path
# ---------------------------------------------------------------------------


class TestCreditFairnessJSON:
    def test_fairness_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "credit", "fairness"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["protected_var"] == "age"
        assert "total_effect" in data
        assert "direct_effect" in data
        assert "legitimate_paths" in data
        assert "concerning_paths" in data

    def test_fairness_custom_protected(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "credit", "fairness", "--protected", "age"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["protected_var"] == "age"


# ---------------------------------------------------------------------------
# Discover: JSON paths
# ---------------------------------------------------------------------------


class TestDiscoverJSON:
    def test_discover_consensus_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(
            [
                "--json",
                "discover",
                "--consensus",
                "--variables",
                "unemployment_rate,income,credit_score",
            ]
        )
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert "edges" in data
        assert "algorithms_used" in data

    def test_discover_pc_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(
            [
                "--json",
                "discover",
                "--variables",
                "unemployment_rate,income,credit_score",
            ]
        )
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert "skeleton" in data
        assert "ci_tests_run" in data


# ---------------------------------------------------------------------------
# Simulate
# ---------------------------------------------------------------------------


class TestSimulate:
    def test_simulate_adverse_text(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["simulate", "--scenario", "adverse"])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Scenario:" in out
        assert "Impact score" in out

    def test_simulate_adverse_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "simulate", "--scenario", "adverse"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert "impact_score" in data
        assert "total_affected" in data

    def test_simulate_baseline(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "simulate", "--scenario", "baseline"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert "steps" in data

    def test_simulate_unknown_scenario(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["simulate", "--scenario", "nonexistent"])
        assert rc == 1
        out = capsys.readouterr().out
        assert "Unknown scenario" in out

    def test_simulate_monte_carlo_text(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["simulate", "--scenario", "adverse", "--monte-carlo", "10"])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Monte Carlo" in out
        assert "10 runs" in out

    def test_simulate_monte_carlo_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "simulate", "--scenario", "adverse", "--monte-carlo", "10"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert "impact_score_mean" in data
        assert "variable_stats" in data


# ---------------------------------------------------------------------------
# Fabric ingest
# ---------------------------------------------------------------------------


class TestFabricIngest:
    def test_ingest_csv(self, capsys: pytest.CaptureFixture, tmp_path: Path) -> None:
        csv_file = tmp_path / "data.csv"
        csv_file.write_text("a,b\n1,2\n3,4\n")
        rc = main(["fabric", "ingest", str(csv_file)])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Ingested" in out
        assert "2 rows" in out

    def test_ingest_csv_json(self, capsys: pytest.CaptureFixture, tmp_path: Path) -> None:
        csv_file = tmp_path / "data.csv"
        csv_file.write_text("x,y\n10,20\n30,40\n50,60\n")
        rc = main(["--json", "fabric", "ingest", str(csv_file)])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["rows"] == 3
        assert data["connector"] == "csv"

    def test_ingest_csv_three_rows(self, capsys: pytest.CaptureFixture, tmp_path: Path) -> None:
        csv_file = tmp_path / "multi.csv"
        csv_file.write_text("col1,col2\nA,1\nB,2\nC,3\n")
        rc = main(["fabric", "ingest", str(csv_file)])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Ingested" in out
        assert "3 rows" in out

    def test_ingest_with_schema(self, capsys: pytest.CaptureFixture, tmp_path: Path) -> None:
        csv_file = tmp_path / "data.csv"
        csv_file.write_text("val\n1\n2\n")
        rc = main(["fabric", "ingest", str(csv_file), "--schema", "credit"])
        assert rc == 0


# ---------------------------------------------------------------------------
# Fabric profile
# ---------------------------------------------------------------------------


class TestFabricProfile:
    def test_profile_text(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["fabric", "profile"])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Data Profile" in out

    def test_profile_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "fabric", "profile"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert "row_count" in data
        assert "column_count" in data
        assert "columns" in data
        assert data["row_count"] > 0


# ---------------------------------------------------------------------------
# Fabric quality JSON path
# ---------------------------------------------------------------------------


class TestFabricQualityJSON:
    def test_quality_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "fabric", "quality"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert "all_passed" in data
        assert "overall_score" in data
        assert "gates" in data


# ---------------------------------------------------------------------------
# Providers: cost, check JSON, list JSON
# ---------------------------------------------------------------------------


class TestProvidersCost:
    def test_providers_cost_text(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["providers", "cost"])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Pricing" in out

    def test_providers_cost_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "providers", "cost"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert "pricing" in data
        assert "usage" in data


class TestProvidersCheckJSON:
    def test_check_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "providers", "check"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert isinstance(data, dict)


class TestProvidersListJSON:
    def test_list_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "providers", "list"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert isinstance(data, dict)


# ---------------------------------------------------------------------------
# Cost: status, set
# ---------------------------------------------------------------------------


class TestCostStatus:
    def test_cost_status_text(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["cost", "status"])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Budget Status" in out
        assert "Monthly budget" in out

    def test_cost_status_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "cost", "status"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert "monthly_budget_usd" in data
        assert "spent_usd" in data
        assert "within_budget" in data


class TestCostSet:
    def test_cost_set_text(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["cost", "set", "100.0"])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Budget set" in out
        assert "$100.00" in out

    def test_cost_set_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "cost", "set", "250.0"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["monthly_budget_usd"] == 250.0
        assert data["updated"] is True


# ---------------------------------------------------------------------------
# Tenant CRUD
# ---------------------------------------------------------------------------


class TestTenantCLI:
    def test_create_text(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["tenant", "create", "test-t1", "--name", "Test One", "--tier", "pro"])
        assert rc == 0
        out = capsys.readouterr().out
        assert "Created tenant" in out
        assert "test-t1" in out
        assert "pro" in out

    def test_create_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "tenant", "create", "test-t2"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["tenant_id"] == "test-t2"

    def test_list_text(self, capsys: pytest.CaptureFixture) -> None:
        # TenantStore is in-memory per engine — fresh call has no tenants
        rc = main(["tenant", "list"])
        assert rc == 0
        out = capsys.readouterr().out
        assert "No tenants" in out or "Tenants" in out

    def test_list_json(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "tenant", "list"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert "tenants" in data

    def test_get_nonexistent(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["tenant", "get", "nonexistent-tenant-xyz"])
        assert rc == 1
        out = capsys.readouterr().out
        assert "not found" in out.lower()

    def test_delete_nonexistent(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["tenant", "delete", "missing-tenant-xyz"])
        assert rc == 1
        out = capsys.readouterr().out
        assert "Not found" in out


# ---------------------------------------------------------------------------
# Serve + Edit (mocked — they block on serve_forever)
# ---------------------------------------------------------------------------


class TestServeEdit:
    def test_serve_dispatches(self) -> None:
        with patch("orxaq.experiences.cli._cmd_serve") as mock_serve:
            mock_serve.return_value = 0
            rc = main(["serve"])
            assert rc == 0
            mock_serve.assert_called_once()

    def test_edit_dispatches(self) -> None:
        with patch("orxaq.experiences.cli._cmd_edit") as mock_edit:
            mock_edit.return_value = 0
            rc = main(["edit"])
            assert rc == 0
            mock_edit.assert_called_once()


# ---------------------------------------------------------------------------
# Model load JSON + versions JSON paths
# ---------------------------------------------------------------------------


class TestModelEdgeCases:
    def test_model_load_json(self, capsys: pytest.CaptureFixture, tmp_path: Path) -> None:
        store = str(tmp_path / "ckpt")
        main(["model", "save", "--name", "mj", "--version", "1.0", "--store", store])
        capsys.readouterr()
        rc = main(["--json", "model", "load", "--name", "mj", "--store", store])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["loaded"] is True
        assert data["name"] == "mj"

    def test_model_load_nonexistent_json(
        self, capsys: pytest.CaptureFixture, tmp_path: Path
    ) -> None:
        store = str(tmp_path / "ckpt")
        rc = main(["--json", "model", "load", "--name", "ghost", "--store", store])
        assert rc == 0  # _json_out always returns 0
        data = json.loads(capsys.readouterr().out)
        assert "error" in data

    def test_model_versions_json(self, capsys: pytest.CaptureFixture, tmp_path: Path) -> None:
        store = str(tmp_path / "ckpt")
        main(["model", "save", "--name", "vj", "--version", "1.0", "--store", store])
        main(["model", "save", "--name", "vj", "--version", "2.0", "--store", store])
        capsys.readouterr()
        rc = main(["--json", "model", "versions", "--name", "vj", "--store", store])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["name"] == "vj"
        assert len(data["versions"]) == 2

    def test_model_versions_empty(self, capsys: pytest.CaptureFixture, tmp_path: Path) -> None:
        store = str(tmp_path / "ckpt")
        rc = main(["model", "versions", "--name", "ghost", "--store", store])
        assert rc == 0
        out = capsys.readouterr().out
        assert "No versions" in out


# ---------------------------------------------------------------------------
# Export ECL JSON to file
# ---------------------------------------------------------------------------


class TestExportEdgeCases:
    def test_export_ecl_json_to_file(self, capsys: pytest.CaptureFixture, tmp_path: Path) -> None:
        outfile = str(tmp_path / "ecl.json")
        rc = main(["--json", "export", "ecl", "--output", outfile])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["exported"] is True
        assert data["records"] > 0

    def test_export_edges_json_to_file(self, capsys: pytest.CaptureFixture, tmp_path: Path) -> None:
        outfile = str(tmp_path / "edges.csv")
        rc = main(["--json", "export", "edges", "--output", outfile])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["exported"] is True

    def test_export_nodes_json_to_file(self, capsys: pytest.CaptureFixture, tmp_path: Path) -> None:
        outfile = str(tmp_path / "nodes.csv")
        rc = main(["--json", "export", "nodes", "--output", outfile])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["exported"] is True


# ---------------------------------------------------------------------------
# Main dispatch edge cases
# ---------------------------------------------------------------------------


class TestMainDispatchEdges:
    def test_no_subcommand_returns_1(self, capsys: pytest.CaptureFixture) -> None:
        rc = main([])
        assert rc == 1

    def test_fabric_no_subcommand(self, capsys: pytest.CaptureFixture) -> None:
        try:
            rc = main(["fabric"])
            assert rc == 1
        except SystemExit as e:
            assert e.code in (0, 1, 2)

    def test_providers_no_subcommand(self, capsys: pytest.CaptureFixture) -> None:
        try:
            rc = main(["providers"])
            assert rc == 1
        except SystemExit as e:
            assert e.code in (0, 1, 2)

    def test_model_no_subcommand(self, capsys: pytest.CaptureFixture) -> None:
        try:
            rc = main(["model"])
            assert rc == 1
        except SystemExit as e:
            assert e.code in (0, 1, 2)

    def test_export_no_subcommand(self, capsys: pytest.CaptureFixture) -> None:
        try:
            rc = main(["export"])
            assert rc == 1
        except SystemExit as e:
            assert e.code in (0, 1, 2)

    def test_cost_no_subcommand(self, capsys: pytest.CaptureFixture) -> None:
        try:
            rc = main(["cost"])
            assert rc == 1
        except SystemExit as e:
            assert e.code in (0, 1, 2)

    def test_tenant_no_subcommand(self, capsys: pytest.CaptureFixture) -> None:
        try:
            rc = main(["tenant"])
            assert rc == 1
        except SystemExit as e:
            assert e.code in (0, 1, 2)


# ---------------------------------------------------------------------------
# _load_data with file input
# ---------------------------------------------------------------------------


class TestLoadDataPaths:
    def test_load_data_csv(self, capsys: pytest.CaptureFixture, tmp_path: Path) -> None:
        csv_file = tmp_path / "port.csv"
        csv_file.write_text(
            "loan_id,pd,lgd,ead,sector,vintage\n"
            "L1,0.05,0.4,100000,Retail,2024Q1\n"
            "L2,0.10,0.5,200000,Corp,2024Q1\n"
        )
        rc = main(["--json", "credit", "portfolio", "--data", str(csv_file)])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["count"] == 2

    def test_load_data_json(self, capsys: pytest.CaptureFixture, tmp_path: Path) -> None:
        json_file = tmp_path / "port.json"
        json_file.write_text(
            json.dumps(
                [
                    {"loan_id": "L1", "pd": 0.05, "lgd": 0.4, "ead": 100000},
                    {"loan_id": "L2", "pd": 0.10, "lgd": 0.5, "ead": 200000},
                    {"loan_id": "L3", "pd": 0.15, "lgd": 0.6, "ead": 300000},
                ]
            )
        )
        rc = main(["--json", "credit", "portfolio", "--data", str(json_file)])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["count"] == 3


# ---------------------------------------------------------------------------
# ECL horizon path
# ---------------------------------------------------------------------------


class TestECLHorizon:
    def test_ecl_with_numeric_horizon(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "credit", "ecl", "--horizon", "4"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        assert data["horizon_quarters"] == 4

    def test_ecl_with_dynamic_horizon(self, capsys: pytest.CaptureFixture) -> None:
        rc = main(["--json", "credit", "ecl", "--horizon", "dynamic"])
        assert rc == 0
        data = json.loads(capsys.readouterr().out)
        # "dynamic" passes horizon=None which uses default method
        assert data["horizon_method"] in ("dynamic", "fixed")
        assert data["total_ecl"] >= 0
