"""Tests for SDK resource namespaces (locations, projects, etc.)."""

import io

import httpx
import pytest

from dalysdk.client import DalyClient

# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


AUTH_RESPONSE = {
    "access_token": "test-jwt",
    "token_type": "bearer",
    "expires_in": 86400,
}


def _mock_transport(
    responses: dict[str, httpx.Response],
    seen_requests: list[httpx.Request] | None = None,
) -> httpx.MockTransport:
    def handler(request: httpx.Request) -> httpx.Response:
        if seen_requests is not None:
            seen_requests.append(request)
        if request.url.path == "/auth/token":
            return httpx.Response(200, json=AUTH_RESPONSE)
        full_path = request.url.path
        if request.url.query:
            full_path = f"{full_path}?{request.url.query.decode()}"
        full_key = f"{request.method} {full_path}"
        if full_key in responses:
            return responses[full_key]
        key = f"{request.method} {request.url.path}"
        if key in responses:
            return responses[key]
        return httpx.Response(404, json={"detail": f"No mock for {key}"})

    return httpx.MockTransport(handler)


def _make_client(
    responses: dict[str, httpx.Response],
    *,
    seen_requests: list[httpx.Request] | None = None,
) -> DalyClient:
    client = DalyClient(
        workspace_api_key="wk_test",
        user_api_key="uk_test",
    )
    client._base_url = "https://mock.dalyenergy.com"
    client._auth._base_url = "https://mock.dalyenergy.com"
    client._http = httpx.Client(
        base_url="https://mock.dalyenergy.com",
        transport=_mock_transport(responses, seen_requests=seen_requests),
    )
    # Pre-set auth token to avoid real HTTP call in AuthManager
    import time

    client._auth._token = "test-jwt-token"
    client._auth._token_expiry = time.time() + 86400
    return client


# ---------------------------------------------------------------------------
# Location resource
# ---------------------------------------------------------------------------


class TestLocationResource:
    def test_list(self):
        client = _make_client(
            {
                "GET /locations": httpx.Response(200, json=[{"id": 1}]),
            }
        )
        result = client.locations.list()
        assert len(result) == 1
        client.close()

    def test_get(self):
        client = _make_client(
            {
                "GET /locations/1": httpx.Response(200, json={"id": 1, "name": "Site"}),
            }
        )
        loc = client.locations.get(1)
        assert loc["name"] == "Site"
        client.close()

    def test_create(self):
        client = _make_client(
            {
                "POST /locations": httpx.Response(200, json={"id": 2}),
            }
        )
        loc = client.locations.create({"latitude": 33.0, "longitude": -112.0})
        assert loc["id"] == 2
        client.close()

    def test_delete(self):
        client = _make_client(
            {
                "DELETE /locations/1": httpx.Response(200, json={"deleted": True}),
            }
        )
        result = client.locations.delete(1)
        assert result["deleted"] is True
        client.close()


# ---------------------------------------------------------------------------
# Project resource
# ---------------------------------------------------------------------------


class TestProjectResource:
    def test_list(self):
        client = _make_client(
            {
                "GET /projects": httpx.Response(200, json=[{"id": 1, "name": "Proj"}]),
            }
        )
        result = client.projects.list()
        assert len(result) == 1
        client.close()

    def test_create(self):
        client = _make_client(
            {
                "POST /projects": httpx.Response(200, json={"id": 2, "name": "New"}),
            }
        )
        result = client.projects.create({"name": "New"})
        assert result["name"] == "New"
        client.close()

    def test_import_from_file_paths(self, tmp_path):
        project_file = tmp_path / "MyProject.PRJ"
        project_file.write_bytes(b"PVObject_=pvSite")

        client = _make_client(
            {
                "POST /projects/import": httpx.Response(
                    200,
                    json={"project": {"id": 12}, "filesProcessed": ["MyProject.PRJ"], "warnings": []},
                ),
            }
        )
        result = client.projects.import_from_files(file_paths=str(project_file))
        assert result["project"]["id"] == 12
        assert result["filesProcessed"] == ["MyProject.PRJ"]
        client.close()

    def test_import_from_file_objects(self):
        client = _make_client(
            {
                "POST /projects/import": httpx.Response(
                    200,
                    json={"project": {"id": 12}, "filesProcessed": ["MyProject.PRJ"], "warnings": []},
                ),
            }
        )
        result = client.projects.import_from_files(file_objs=[("MyProject.PRJ", io.BytesIO(b"PVObject_=pvSite"))])
        assert result["project"]["id"] == 12
        assert result["filesProcessed"] == ["MyProject.PRJ"]
        client.close()

    def test_import_requires_files(self):
        client = _make_client(
            {
                "POST /projects/import": httpx.Response(200, json={"ok": True}),
            }
        )
        with pytest.raises(ValueError, match="Provide at least one project file"):
            client.projects.import_from_files()
        client.close()

    def test_import_file_object_requires_filename(self):
        client = _make_client(
            {
                "POST /projects/import": httpx.Response(200, json={"ok": True}),
            }
        )
        with pytest.raises(ValueError, match="non-empty filename"):
            client.projects.import_from_files(file_objs=[("", io.BytesIO(b"data"))])
        client.close()


# ---------------------------------------------------------------------------
# Module resource
# ---------------------------------------------------------------------------


class TestModuleResource:
    def test_list(self):
        client = _make_client(
            {
                "GET /modules": httpx.Response(200, json=[{"id": 1, "model": "Test-400"}]),
            }
        )
        result = client.modules.list()
        assert len(result) == 1
        client.close()


# ---------------------------------------------------------------------------
# Inverter resource
# ---------------------------------------------------------------------------


class TestInverterResource:
    def test_list(self):
        client = _make_client(
            {
                "GET /inverters": httpx.Response(200, json=[{"id": 1, "model": "INV-50"}]),
            }
        )
        result = client.inverters.list()
        assert len(result) == 1
        client.close()


# ---------------------------------------------------------------------------
# Task resource
# ---------------------------------------------------------------------------


class TestTaskResource:
    def test_list(self):
        client = _make_client(
            {
                "GET /tasks": httpx.Response(200, json={"tasks": []}),
            }
        )
        result = client.tasks.list()
        assert "tasks" in result
        client.close()

    def test_get(self):
        client = _make_client(
            {
                "GET /tasks/1": httpx.Response(200, json={"id": 1, "status": "COMPLETED"}),
            }
        )
        result = client.tasks.get(1)
        assert result["status"] == "COMPLETED"
        client.close()


# ---------------------------------------------------------------------------
# Energy model resource
# ---------------------------------------------------------------------------


class TestEnergyModelResource:
    def test_list(self):
        client = _make_client(
            {
                "GET /energymodels": httpx.Response(200, json=[]),
            }
        )
        result = client.energy_models.list()
        assert result == []
        client.close()

    def test_list_include_summary(self):
        requests: list[httpx.Request] = []
        client = _make_client(
            {
                "GET /energymodels": httpx.Response(
                    200,
                    json=[{"id": 9, "annualProductionMWh": 412.3, "capacityFactor": 0.26}],
                ),
            },
            seen_requests=requests,
        )
        result = client.energy_models.list(include="summary")
        assert result[0]["annualProductionMWh"] == 412.3
        assert requests[-1].url.params.get("include") == "summary"
        client.close()

    def test_list_rejects_invalid_include_token(self):
        client = _make_client(
            {
                "GET /energymodels": httpx.Response(200, json=[]),
            }
        )
        with pytest.raises(ValueError, match="Invalid include token"):
            client.energy_models.list(include=["summary", "inputs"])
        client.close()

    def test_get_detail_include_tokens(self):
        requests: list[httpx.Request] = []
        client = _make_client(
            {
                "GET /energymodels/1": httpx.Response(
                    200,
                    json={
                        "id": 1,
                        "projectId": 10,
                        "globalProjectId": 7001,
                        "annualProductionMWh": 900.5,
                        "revision": "v0003",
                    },
                ),
            },
            seen_requests=requests,
        )
        result = client.energy_models.get(1, include=["summary", "inputs"])
        assert result["globalProjectId"] == 7001
        assert result["revision"] == "v0003"
        assert requests[-1].url.params.get("include") == "summary,inputs"
        client.close()

    def test_get_with_inputs_uses_include_inputs(self):
        requests: list[httpx.Request] = []
        client = _make_client(
            {
                "GET /energymodels/1": httpx.Response(
                    200,
                    json={
                        "energyModelId": 1,
                        "status": "draft",
                        "inputs": {"projectId": 10, "blocks": []},
                    },
                ),
            },
            seen_requests=requests,
        )
        result = client.energy_models.get_with_inputs(1)
        assert result["inputs"]["projectId"] == 10
        assert requests[-1].url.params.get("include") == "inputs"
        client.close()

    def test_create_async_uses_task_details_envelope(self):
        requests: list[httpx.Request] = []
        client = _make_client(
            {
                "POST /energymodels": httpx.Response(
                    202,
                    json={
                        "result": "success",
                        "message": "Energy model queued for processing",
                        "taskDetails": {"taskId": 21, "status": "queued"},
                        "energyModelId": 12,
                        "runIndex": 4,
                        "projectId": 10,
                        "globalProjectId": 7001,
                    },
                ),
            },
            seen_requests=requests,
        )
        result = client.energy_models.create({"projectId": 10, "blocks": []}, include="summary")
        assert result["taskDetails"]["taskId"] == 21
        assert result["energyModelId"] == 12
        assert result["globalProjectId"] == 7001
        assert requests[-1].url.params.get("include") is None
        client.close()

    def test_create_accepted_response_has_no_outputs_wrapper_assumption(self):
        client = _make_client(
            {
                "POST /energymodels": httpx.Response(
                    202,
                    json={
                        "result": "success",
                        "energyModelId": 22,
                        "message": "Energy model queued for processing",
                        "taskDetails": {"taskId": 33, "executionMode": "async_queued"},
                        "projectId": 10,
                        "globalProjectId": 7001,
                    },
                ),
            }
        )
        result = client.energy_models.create({"projectId": 10, "blocks": []})
        assert "outputs" not in result
        assert result["taskDetails"]["taskId"] == 33
        assert result["projectId"] == 10
        assert result["globalProjectId"] == 7001
        client.close()

    def test_create_async_keeps_client_default_timeout_when_not_overridden(self):
        requests: list[httpx.Request] = []
        client = _make_client(
            {
                "POST /energymodels": httpx.Response(
                    202,
                    json={"result": "success", "energyModelId": 22, "taskDetails": {"taskId": 33}},
                ),
            },
            seen_requests=requests,
        )

        client.energy_models.create({"projectId": 10, "blocks": []}, async_mode=True)

        assert requests[-1].extensions["timeout"] == {"connect": 5.0, "read": 5.0, "write": 5.0, "pool": 5.0}
        client.close()

    def test_create_forwards_canonical_output_controls_unchanged(self):
        requests: list[httpx.Request] = []
        client = _make_client(
            {
                "POST /energymodels": httpx.Response(
                    202,
                    json={
                        "energyModelId": 22,
                        "taskDetails": {"taskId": 33, "executionMode": "async_queued"},
                    },
                ),
            },
            seen_requests=requests,
        )
        payload = {
            "projectId": 10,
            "blocks": [],
            "output": {
                "timeSeries": True,
                "blockResults": True,
                "blockIndex": 1,
                "lossBreakdownTimestamps": True,
                "irradianceLossDetail": True,
            },
        }

        result = client.energy_models.create(payload)

        assert result["taskDetails"]["taskId"] == 33
        req = requests[-1]
        posted = req.read().decode("utf-8")
        assert '"blockResults":true' in posted
        assert '"blockIndex":1' in posted
        assert '"lossBreakdownTimestamps":true' in posted
        assert '"irradianceLossDetail":true' in posted
        client.close()

    def test_update_replaces_saved_input_json(self):
        requests: list[httpx.Request] = []
        client = _make_client(
            {
                "PUT /energymodels/2": httpx.Response(
                    200,
                    json={
                        "energyModelId": 2,
                        "status": "draft",
                        "taskDetails": {"message": "Energy model saved"},
                        "inputs": {"projectId": 10, "blocks": []},
                    },
                ),
            },
            seen_requests=requests,
        )
        payload = {"projectId": 10, "blocks": []}
        result = client.energy_models.update(2, payload)
        assert result["status"] == "draft"
        assert result["inputs"]["projectId"] == 10
        assert requests[-1].method == "PUT"
        client.close()

    def test_update_forwards_canonical_output_controls_unchanged(self):
        requests: list[httpx.Request] = []
        client = _make_client(
            {
                "PUT /energymodels/2": httpx.Response(
                    200,
                    json={
                        "energyModelId": 2,
                        "status": "draft",
                        "inputs": {
                            "projectId": 10,
                            "blocks": [],
                            "output": {
                                "timeSeries": True,
                                "blockResults": True,
                                "blockIndex": 1,
                                "lossBreakdownTimestamps": True,
                            },
                        },
                    },
                ),
            },
            seen_requests=requests,
        )
        payload = {
            "projectId": 10,
            "blocks": [],
            "output": {
                "timeSeries": True,
                "blockResults": True,
                "blockIndex": 1,
                "lossBreakdownTimestamps": True,
            },
        }

        result = client.energy_models.update(2, payload)

        assert result["inputs"]["output"]["blockIndex"] == 1
        posted = requests[-1].read().decode("utf-8")
        assert '"blockResults":true' in posted
        assert '"blockIndex":1' in posted
        assert '"lossBreakdownTimestamps":true' in posted
        client.close()

    def test_create_include_is_ignored_for_api_compatibility(self):
        requests: list[httpx.Request] = []
        client = _make_client(
            {
                "POST /energymodels": httpx.Response(
                    202,
                    json={"result": "success", "energyModelId": 12, "taskDetails": {"taskId": 21}},
                ),
            },
            seen_requests=requests,
        )
        client.energy_models.create({"projectId": 10, "blocks": []}, include=["summary", "inputs"])
        assert requests[-1].url.params.get("include") is None
        client.close()

    def test_run_saved_accepted_uses_existing_row_path(self):
        requests: list[httpx.Request] = []
        client = _make_client(
            {
                "POST /energymodels/2/run": httpx.Response(
                    202,
                    json={
                        "result": "success",
                        "message": "Energy model queued for processing",
                        "energyModelId": 2,
                        "taskDetails": {"taskId": 33, "executionMode": "async_queued"},
                    },
                ),
            },
            seen_requests=requests,
        )
        result = client.energy_models.run_saved(2)
        assert result["energyModelId"] == 2
        assert requests[-1].url.path == "/energymodels/2/run"
        client.close()

    def test_run_saved_async_accepts_timeout_override(self):
        requests: list[httpx.Request] = []
        client = _make_client(
            {
                "POST /energymodels/2/run": httpx.Response(
                    202,
                    json={
                        "result": "success",
                        "message": "Energy model queued for processing",
                        "energyModelId": 2,
                        "taskDetails": {"taskId": 34, "executionMode": "async_queued"},
                    },
                ),
            },
            seen_requests=requests,
        )
        result = client.energy_models.run_saved(2, async_mode=True, timeout=120)
        assert result["taskDetails"]["executionMode"] == "async_queued"
        assert requests[-1].extensions["timeout"] == {"connect": 120, "read": 120, "write": 120, "pool": 120}
        client.close()

    def test_legacy_query_accepts_legacy_block_controls(self):
        requests: list[httpx.Request] = []
        client = _make_client(
            {
                "POST /energymodels": httpx.Response(
                    202, json={"result": "success", "energyModelId": 22, "taskDetails": {"taskId": 33}}
                ),
            },
            seen_requests=requests,
        )

        client.energy_models.create_legacy_query(
            {"projectId": 10, "blocks": [{"name": "Block A"}]},
            async_mode=True,
            block_results=True,
            block_results_index=0,
        )

        assert requests[-1].url.params.get("blockresults") == "true"
        client.close()

    def test_detail_revision_optional(self):
        client = _make_client(
            {
                "GET /energymodels/2": httpx.Response(
                    200,
                    json={
                        "id": 2,
                        "projectId": 10,
                        "globalProjectId": 7001,
                    },
                ),
            }
        )
        result = client.energy_models.get(2)
        assert result["projectId"] == 10
        assert result["globalProjectId"] == 7001
        assert "revision" not in result
        client.close()


# ---------------------------------------------------------------------------
# Shading scene resource
# ---------------------------------------------------------------------------


class TestShadingSceneResource:
    def test_create_from_json(self):
        client = _make_client(
            {
                "POST /shadingscenes": httpx.Response(
                    200,
                    json={
                        "shadingSceneId": 3,
                        "taskId": 7,
                        "status": "processing",
                    },
                ),
            }
        )
        result = client.shading_scenes.create_from_json(
            name="API JSON scene",
            scene_source="api-json",
            scene_json={
                "tableList": [
                    {
                        "tableHash": "table-hash-1",
                        "tracker": False,
                        "xpoints": [0, 1, 1, 0],
                        "ypoints": [0, 0, 1, 1],
                        "zpoints": [0, 0, 0, 0],
                        "torqueTubePoint_1": [0, 0, 0],
                        "torqueTubePoint_2": [1, 0, 0],
                        "torqueTubeVector": [1, 0, 0],
                        "transverseDiodes": 10,
                        "stringsPerLength": 1,
                    }
                ],
                "obstructions": [],
                "modelInputs": {"diodesReadIn": True, "diffuseTableIndex": 0},
            },
        )
        assert result["shadingSceneId"] == 3
        assert result["status"] == "processing"
        client.close()

    def test_import_file(self):
        client = _make_client(
            {
                "POST /shadingscenes/import": httpx.Response(
                    200,
                    json={
                        "shadingSceneId": 4,
                        "taskId": 8,
                        "status": "processing",
                    },
                ),
            }
        )
        result = client.shading_scenes.import_file(
            name="Imported scene",
            file_obj=io.BytesIO(b"scene-bytes"),
            filename="scene.dae",
            file_type="pvc",
        )
        assert result["shadingSceneId"] == 4
        assert result["taskId"] == 8
        client.close()

    def test_get_results_returns_pending_payload(self):
        client = _make_client(
            {
                "GET /shadingscenes/4/results": httpx.Response(
                    200,
                    json={"status": "pending", "results": None},
                ),
            }
        )
        result = client.shading_scenes.get_results(4)
        assert result == {"status": "pending", "results": None}
        client.close()
