from dataclasses import dataclass

import pytest

from app.core.orchestration.engines import (
    EngineCapability,
    EngineCapabilityUnsupported,
    KestraAdapter,
    N8nAdapter,
    WorkflowEngine,
)


class FakeN8nClient:
    def __init__(self):
        self.calls = []

    def create_workflow(self, definition):
        self.calls.append(("create", definition))
        return {"id": "n8n-42", "active": False}

    def activate_workflow(self, workflow_id):
        self.calls.append(("activate", workflow_id))
        return {"id": workflow_id, "active": True}

    def deactivate_workflow(self, workflow_id):
        self.calls.append(("deactivate", workflow_id))
        return {"id": workflow_id, "active": False}

    def execute_workflow_webhook(self, webhook_path, data=None, method="POST"):
        self.calls.append(("execute", webhook_path, data, method))
        return {"executionId": "n8n-exec-1"}

    def get_execution(self, execution_id, include_data=True):
        self.calls.append(("get_execution", execution_id, include_data))
        return {"id": execution_id, "status": "success"}


@dataclass
class FakeResponse:
    payload: object
    status_code: int = 200

    @property
    def content(self):
        return b"payload"

    @property
    def text(self):
        return str(self.payload)

    def json(self):
        return self.payload

    def raise_for_status(self):
        if self.status_code >= 400:
            raise RuntimeError(f"HTTP {self.status_code}")


class RecordingSession:
    def __init__(self):
        self.calls = []

    def request(self, method, url, **kwargs):
        self.calls.append((method, url, kwargs))
        return FakeResponse({"method": method, "url": url})


KESTRA_YAML = """\
id: flow_1
namespace: dataops.dev
disabled: true
tasks:
  - id: run
    type: io.kestra.plugin.core.log.Log
    message: safe
triggers:
  - id: schedule
    type: io.kestra.plugin.core.trigger.Schedule
    cron: "0 * * * *"
    disabled: true
"""


def test_n8n_adapter_preserves_supported_operations_and_fails_closed():
    client = FakeN8nClient()
    adapter = N8nAdapter(client)

    assert isinstance(adapter, WorkflowEngine)
    assert EngineCapability.DEPLOY_DISABLED in adapter.capabilities
    assert EngineCapability.PAUSE not in adapter.capabilities

    assert adapter.validate(
        {"name": "flow", "nodes": [], "connections": {}}
    ) == {"valid": True}
    assert adapter.deploy_disabled(
        {"name": "flow", "nodes": [], "connections": {}}
    )["id"] == "n8n-42"
    adapter.activate("ignored", "n8n-42")
    adapter.deactivate("ignored", "n8n-42")
    adapter.execute(
        "ignored",
        "n8n-42",
        {"batch": 7},
        webhook_path="dataops/n8n-42",
    )
    adapter.get_execution("n8n-exec-1")

    assert client.calls == [
        ("create", {"name": "flow", "nodes": [], "connections": {}}),
        ("activate", "n8n-42"),
        ("deactivate", "n8n-42"),
        ("execute", "dataops/n8n-42", {"batch": 7}, "POST"),
        ("get_execution", "n8n-exec-1", True),
    ]

    for operation, args in (
        ("pause", ("execution-1",)),
        ("kill", ("execution-1",)),
        ("replay", ("execution-1",)),
        (
            "backfill",
            ("ignored", "n8n-42", "schedule", "2026-07-01", "2026-07-02"),
        ),
        ("get_logs", ("execution-1",)),
    ):
        with pytest.raises(EngineCapabilityUnsupported, match=operation):
            getattr(adapter, operation)(*args)


def test_n8n_execute_requires_an_explicit_governed_webhook_path():
    adapter = N8nAdapter(FakeN8nClient())
    with pytest.raises(EngineCapabilityUnsupported, match="webhook_path"):
        adapter.execute("ignored", "n8n-42", {})


def test_kestra_adapter_maps_common_lifecycle_to_official_api_paths():
    session = RecordingSession()
    adapter = KestraAdapter(
        base_url="http://kestra:8080/api/v1",
        tenant_id="main",
        username="admin@dataops.local",
        password="test-only",
        session=session,
    )

    assert isinstance(adapter, WorkflowEngine)
    assert adapter.capabilities == frozenset(EngineCapability)

    adapter.validate(KESTRA_YAML)
    adapter.deploy_disabled(KESTRA_YAML)
    adapter.activate("dataops.dev", "flow_1")
    adapter.deactivate("dataops.dev", "flow_1")
    adapter.execute("dataops.dev", "flow_1", {"batch": 7})
    adapter.pause("execution-1")
    adapter.kill("execution-1")
    adapter.replay("execution-1")
    adapter.backfill(
        "dataops.dev",
        "flow_1",
        "schedule",
        "2026-07-01T00:00:00Z",
        "2026-07-02T00:00:00Z",
    )
    adapter.get_execution("execution-1")
    adapter.get_logs("execution-1")

    assert [(method, url) for method, url, _ in session.calls] == [
        ("POST", "http://kestra:8080/api/v1/main/flows/validate"),
        ("POST", "http://kestra:8080/api/v1/main/flows/validate"),
        ("POST", "http://kestra:8080/api/v1/main/flows"),
        ("POST", "http://kestra:8080/api/v1/main/flows/enable/by-ids"),
        ("POST", "http://kestra:8080/api/v1/main/flows/disable/by-ids"),
        ("POST", "http://kestra:8080/api/v1/main/executions/dataops.dev/flow_1"),
        ("POST", "http://kestra:8080/api/v1/main/executions/execution-1/pause"),
        ("DELETE", "http://kestra:8080/api/v1/main/executions/execution-1/kill"),
        ("POST", "http://kestra:8080/api/v1/main/executions/execution-1/replay"),
        ("PUT", "http://kestra:8080/api/v1/main/triggers"),
        ("GET", "http://kestra:8080/api/v1/main/executions/execution-1"),
        ("GET", "http://kestra:8080/api/v1/main/logs/execution-1"),
    ]
    assert session.calls[0][2]["data"] == KESTRA_YAML
    assert session.calls[2][2]["data"] == KESTRA_YAML
    assert session.calls[5][2]["files"]["batch"] == (None, "7")
    assert session.calls[9][2]["json"]["backfill"] == {
        "start": "2026-07-01T00:00:00Z",
        "end": "2026-07-02T00:00:00Z",
    }


def test_kestra_deploy_refuses_a_flow_with_enabled_triggers():
    adapter = KestraAdapter(
        base_url="http://kestra:8080/api/v1",
        username="admin@dataops.local",
        password="test-only",
        session=RecordingSession(),
    )

    with pytest.raises(ValueError, match="disabled"):
        adapter.deploy_disabled(KESTRA_YAML.replace("disabled: true", "disabled: false"))


def test_kestra_deploy_requires_the_flow_itself_to_be_disabled():
    adapter = KestraAdapter(
        base_url="http://kestra:8080/api/v1",
        username="admin@dataops.local",
        password="test-only",
        session=RecordingSession(),
    )
    source = """\
id: flow_1
namespace: dataops.dev
tasks:
  - id: run
    type: io.kestra.plugin.core.log.Log
    message: safe
"""

    with pytest.raises(ValueError, match="flow-level disabled"):
        adapter.deploy_disabled(source)
