from __future__ import annotations

import html

from fastapi import status
from fastapi.testclient import TestClient

from models import MiningOperationType, ResourceType, ScenarioStatus


def _create_project(client: TestClient, name: str = "Alpha Project") -> dict:
    payload = {
        "name": name,
        "location": "Chile",
        "operation_type": MiningOperationType.OPEN_PIT.value,
        "description": "Initial feasibility study",
    }
    response = client.post("/projects", json=payload)
    assert response.status_code == 201, response.text
    return response.json()


def _create_scenario(
    client: TestClient,
    project_id: int,
    *,
    name: str = "Scenario A",
    status: ScenarioStatus = ScenarioStatus.DRAFT,
    currency: str | None = "USD",
    primary_resource: ResourceType | None = ResourceType.DIESEL,
) -> dict:
    payload = {
        "name": name,
        "status": status.value,
    }
    if currency:
        payload["currency"] = currency
    if primary_resource:
        payload["primary_resource"] = primary_resource.value

    response = client.post(
        f"/projects/{project_id}/scenarios",
        json=payload,
    )
    assert response.status_code == 201, response.text
    return response.json()


def test_project_crud_cycle(client: TestClient) -> None:
    project = _create_project(client)
    project_id = project["id"]

    fetch_response = client.get(f"/projects/{project_id}")
    assert fetch_response.status_code == 200
    assert fetch_response.json()["name"] == "Alpha Project"

    list_response = client.get("/projects")
    project_ids = {item["id"] for item in list_response.json()}
    assert project_id in project_ids

    update_payload = {
        "description": "Updated project description", "location": "Peru"}
    update_response = client.put(
        f"/projects/{project_id}", json=update_payload)
    assert update_response.status_code == 200
    assert update_response.json(
    )["description"] == "Updated project description"
    assert update_response.json()["location"] == "Peru"

    delete_response = client.delete(f"/projects/{project_id}")
    assert delete_response.status_code == 204

    missing_response = client.get(f"/projects/{project_id}")
    assert missing_response.status_code == 404


def test_project_creation_conflict_returns_409(client: TestClient) -> None:
    _create_project(client, name="Conflict Project")
    conflict_payload = {
        "name": "Conflict Project",
        "location": "Canada",
        "operation_type": MiningOperationType.OTHER.value,
        "description": "Duplicate entry",
    }
    response = client.post("/projects", json=conflict_payload)
    assert response.status_code == 409
    assert "violates" in response.json()["detail"].lower()


def test_create_project_requires_valid_operation_type(client: TestClient) -> None:
    invalid_payload = {
        "name": "Invalid Operation",
        "location": "Australia",
        "operation_type": "INVALID",
        "description": "Bad op type",
    }
    response = client.post("/projects", json=invalid_payload)
    assert response.status_code == 422
    body = response.json()
    assert body["detail"][0]["loc"][-1] == "operation_type"


def test_scenario_crud_cycle(client: TestClient) -> None:
    project = _create_project(client)
    project_id = project["id"]

    creation_payload = {
        "name": "Scenario A",
        "description": "Base case assumptions",
        "status": ScenarioStatus.DRAFT.value,
        "start_date": "2025-01-01",
        "end_date": "2026-01-01",
        "discount_rate": 8.5,
        "currency": "usd",
        "primary_resource": ResourceType.DIESEL.value,
    }
    create_response = client.post(
        f"/projects/{project_id}/scenarios",
        json=creation_payload,
    )
    assert create_response.status_code == 201, create_response.text
    scenario = create_response.json()
    scenario_id = scenario["id"]
    assert scenario["currency"] == "USD"
    assert scenario["project_id"] == project_id

    list_response = client.get(f"/projects/{project_id}/scenarios")
    assert list_response.status_code == 200
    listed_ids = {item["id"] for item in list_response.json()}
    assert scenario_id in listed_ids

    update_payload = {"description": "Revised assumptions",
                      "status": ScenarioStatus.ACTIVE.value}
    update_response = client.put(
        f"/scenarios/{scenario_id}", json=update_payload)
    assert update_response.status_code == 200
    updated = update_response.json()
    assert updated["description"] == "Revised assumptions"
    assert updated["status"] == ScenarioStatus.ACTIVE.value

    delete_response = client.delete(f"/scenarios/{scenario_id}")
    assert delete_response.status_code == 204

    missing_response = client.get(f"/scenarios/{scenario_id}")
    assert missing_response.status_code == 404


def test_create_scenario_requires_existing_project(client: TestClient) -> None:
    payload = {
        "name": "Orphan Scenario",
        "status": ScenarioStatus.DRAFT.value,
    }
    response = client.post("/projects/999/scenarios", json=payload)
    assert response.status_code == 404


def test_create_scenario_conflict_returns_409(client: TestClient) -> None:
    project = _create_project(client, name="Scenario Container")
    project_id = project["id"]

    payload = {"name": "Duplicate Scenario"}
    first_response = client.post(
        f"/projects/{project_id}/scenarios", json=payload)
    assert first_response.status_code == 201

    conflict_response = client.post(
        f"/projects/{project_id}/scenarios", json=payload)
    assert conflict_response.status_code == 409
    assert "constraints" in conflict_response.json()["detail"].lower()


def test_create_scenario_invalid_currency_returns_422(client: TestClient) -> None:
    project = _create_project(client, name="Currency Project")
    project_id = project["id"]
    payload = {
        "name": "Bad Currency",
        "currency": "zz",
    }
    response = client.post(f"/projects/{project_id}/scenarios", json=payload)
    assert response.status_code == 422
    detail = response.json()["detail"][0]
    assert detail["loc"][-1] == "currency"
    assert "invalid currency code" in detail["msg"].lower()


def test_list_scenarios_missing_project_returns_404(client: TestClient) -> None:
    response = client.get("/projects/424242/scenarios")
    assert response.status_code == 404


def test_project_detail_page_renders_scenario_list(client: TestClient) -> None:
    project = _create_project(client, name="UI Detail Project")
    project_id = project["id"]

    _create_scenario(
        client,
        project_id,
        name="Scenario UI",
        status=ScenarioStatus.ACTIVE,
        currency="USD",
    )

    response = client.get(f"/projects/{project_id}/view")
    assert response.status_code == 200
    body = response.text
    assert "scenario-list" in body
    assert "status-pill--active" in body


def test_scenario_list_page_shows_calculator_shortcuts(client: TestClient) -> None:
    project = _create_project(client, name="Portfolio Project")
    project_id = project["id"]
    scenario = _create_scenario(
        client,
        project_id,
        name="Portfolio Scenario",
        status=ScenarioStatus.ACTIVE,
        currency="USD",
    )

    response = client.get(f"/projects/{project_id}/scenarios/ui")
    assert response.status_code == 200
    body = response.text
    unescaped = html.unescape(body)

    assert "Scenario Portfolio" in body
    assert project["name"] in body
    assert scenario["name"] in body
    assert f"projects/{project_id}/view" in unescaped
    assert f"scenarios/{scenario['id']}/view" in unescaped
    expected_calc_fragment = (
        f"calculations/projects/{project_id}/scenarios/{scenario['id']}/profitability"
    )
    assert expected_calc_fragment in unescaped


def test_scenario_detail_page_links_back_to_portfolio(client: TestClient) -> None:
    project = _create_project(client, name="Detail Project")
    scenario = _create_scenario(
        client,
        project["id"],
        name="Detail Scenario",
        status=ScenarioStatus.ACTIVE,
        currency="USD",
        primary_resource=ResourceType.ELECTRICITY,
    )

    response = client.get(f"/scenarios/{scenario['id']}/view")
    assert response.status_code == 200
    body = response.text
    unescaped = html.unescape(body)

    assert project["name"] in body
    assert scenario["name"] in body
    assert "Scenario Overview" in body
    assert f"projects/{project['id']}/scenarios/ui" in unescaped
    assert (
        f"calculations/projects/{project['id']}/scenarios/{scenario['id']}/profitability"
        in unescaped
    )


def test_scenario_form_includes_project_context_guidance(client: TestClient) -> None:
    project = _create_project(client, name="Form Project")

    response = client.get(f"/projects/{project['id']}/scenarios/new")
    assert response.status_code == 200
    body = response.text

    assert "Project Context" in body
    assert project["name"] in body
    assert "Status Guidance" in body
    assert "Baseline Reminder" in body
    assert "Defaults to" in body


def test_calculator_headers_surface_scenario_navigation(client: TestClient) -> None:
    project = _create_project(client, name="Calc Project")
    scenario = _create_scenario(
        client,
        project["id"],
        name="Calc Scenario",
        status=ScenarioStatus.DRAFT,
        currency="USD",
    )

    profitability = client.get(
        f"/calculations/projects/{project['id']}/scenarios/{scenario['id']}/profitability"
    )
    assert profitability.status_code == 200
    profitability_body = html.unescape(profitability.text)
    assert f"scenarios/{scenario['id']}/view" in profitability_body
    assert f"projects/{project['id']}/scenarios/ui" in profitability_body
    assert (
        f"calculations/projects/{project['id']}/scenarios/{scenario['id']}/profitability"
        in profitability_body
    )

    capex = client.get(
        f"/calculations/capex?project_id={project['id']}&scenario_id={scenario['id']}"
    )
    assert capex.status_code == 200
    capex_body = html.unescape(capex.text)
    assert f"scenarios/{scenario['id']}/view" in capex_body
    assert f"projects/{project['id']}/scenarios/ui" in capex_body

    opex = client.get(
        f"/calculations/opex?project_id={project['id']}&scenario_id={scenario['id']}"
    )
    assert opex.status_code == 200
    opex_body = html.unescape(opex.text)
    assert f"scenarios/{scenario['id']}/view" in opex_body
    assert f"projects/{project['id']}/scenarios/ui" in opex_body


def test_profitability_legacy_endpoint_redirects_to_scenario_path(client: TestClient) -> None:
    project = _create_project(client, name="Redirect Project")
    scenario = _create_scenario(
        client,
        project["id"],
        name="Redirect Scenario",
        status=ScenarioStatus.ACTIVE,
        currency="USD",
    )

    response = client.get(
        f"/calculations/profitability?project_id={project['id']}&scenario_id={scenario['id']}",
        follow_redirects=False,
    )

    assert response.status_code == status.HTTP_307_TEMPORARY_REDIRECT
    location = response.headers.get("location")
    assert location is not None
    expected_path = (
        f"/calculations/projects/{project['id']}/scenarios/{scenario['id']}/profitability"
    )
    assert expected_path in location
