import hashlib
import hmac
import json
import pytest
from fastapi.testclient import TestClient
from self_governance.github_app import app
from self_governance.learning import LEARNING_STATE_FILE
import os
from typing import Optional, Any

client = TestClient(app)


def signed_post(payload: dict, event: str, extra_headers: Optional[dict[Any, Any]] = None):
    """POST /webhook with a valid HMAC signature for the test secret."""
    body = json.dumps(payload).encode()
    sig = hmac.new(
        os.environ["WEBHOOK_SECRET"].encode(), body, hashlib.sha256
    ).hexdigest()
    headers = {"X-GitHub-Event": event, "X-Hub-Signature-256": f"sha256={sig}"}
    if extra_headers:
        headers.update(extra_headers)
    return client.post("/webhook", content=body, headers=headers)


@pytest.fixture(autouse=True)
def clean_learning_state():
    if os.path.exists(LEARNING_STATE_FILE):
        os.remove(LEARNING_STATE_FILE)
    yield
    if os.path.exists(LEARNING_STATE_FILE):
        os.remove(LEARNING_STATE_FILE)


def test_webhook_ping():
    response = signed_post({}, "ping")
    assert response.status_code == 200
    assert response.json() == {"status": "ok", "msg": "pong"}


def test_webhook_issue_opened(tmp_path):
    (tmp_path / ".planning").mkdir(parents=True, exist_ok=True)
    # Mock working directory where handoff is written
    payload = {
        "action": "opened",
        "issue": {
            "title": "Performance regression in dynamic dimensioning",
            "body": "The LazyList lookup is slow",
        },
    }

    # We create a dummy handoff file so the trigger succeeds in the webhook folder
    os.makedirs(tmp_path, exist_ok=True)

    # Patch working directory to prevent writing to real root during test
    from unittest.mock import patch

    with patch("self_governance.github_app.nudger.working_directory", str(tmp_path)):
        # Write candidates file in the mocked directory
        handoff_path = os.path.join(str(tmp_path), ".planning/CURRENT_STATE.md")
        with open(handoff_path, "w") as f:
            f.write("status: COMPLETED\ncandidates:\n  - agent_1\n")

        response = signed_post(payload, "issues")
        assert response.status_code == 200
        json_data = response.json()
        assert json_data["status"] == "success"
        assert json_data["requirements"][0] == 5.0  # Heuristic triggered 'performance'


def test_webhook_pr_closed_merged():
    payload = {
        "action": "closed",
        "pull_request": {
            "merged": True,
            "created_at_timestamp": 100.0,
            "closed_at_timestamp": 125.0,
            "title": "Fix security vulnerability CVE-1234",
        },
    }

    response = signed_post(payload, "pull_request")
    assert response.status_code == 200
    assert response.json()["status"] == "success"

    # Verify learning state holds security alert counts
    from self_governance.learning import get_learning_state

    state = get_learning_state()
    assert state["vulnerability_counts"] == 1
    assert state["average_cycle_time"] == 25.0


def test_webhook_hmac_verification(monkeypatch):
    import hmac
    import hashlib

    # Configure webhook secret environment
    monkeypatch.setenv("WEBHOOK_SECRET", "super_secret_token")

    # Send request without signature
    response = client.post("/webhook", json={}, headers={"X-GitHub-Event": "ping"})
    assert response.status_code == 401
    assert response.json()["detail"] == "Missing X-Hub-Signature-256 header"

    # Send request with invalid signature
    response = client.post(
        "/webhook",
        json={},
        headers={
            "X-GitHub-Event": "ping",
            "X-Hub-Signature-256": "sha256=invalid_hash",
        },
    )
    assert response.status_code == 401
    assert response.json()["detail"] == "Invalid signature"

    # Send request with valid signature
    body_data = b"{}"
    expected_hash = hmac.new(
        b"super_secret_token", body_data, hashlib.sha256
    ).hexdigest()
    response = client.post(
        "/webhook",
        content=body_data,
        headers={
            "X-GitHub-Event": "ping",
            "X-Hub-Signature-256": f"sha256={expected_hash}",
        },
    )
    assert response.status_code == 200
    assert response.json() == {"status": "ok", "msg": "pong"}


def test_webhook_mandatory_secret():
    import subprocess
    import sys
    import os
    cmd = [sys.executable, "-c", "import self_governance.github_app"]
    env = os.environ.copy()
    env.pop("WEBHOOK_SECRET", None)
    env["TESTING"] = "False"
    result = subprocess.run(cmd, env=env, capture_output=True, text=True)
    assert result.returncode != 0
    assert "ValueError: WEBHOOK_SECRET environment variable is required" in result.stderr


def test_webhook_non_dict_payload():
    # Test that a list payload raises 400 with "Invalid structure"
    body_data = b"[]"
    import hmac
    import hashlib
    import os
    secret = os.environ.get("WEBHOOK_SECRET", "dummy_secret")
    sig = hmac.new(
        secret.encode(), body_data, hashlib.sha256
    ).hexdigest()
    
    response = client.post(
        "/webhook",
        content=body_data,
        headers={
            "X-GitHub-Event": "issues",
            "X-Hub-Signature-256": f"sha256={sig}",
        },
    )
    assert response.status_code == 400
    assert response.json()["detail"] == "Invalid structure"


def test_webhook_pr_none_safety():
    payload = {
        "action": "closed",
        "pull_request": {
            "merged": True,
            "created_at_timestamp": None,
            "closed_at_timestamp": None,
            "title": "Fix security vulnerability CVE-1234",
        },
    }
    response = signed_post(payload, "pull_request")
    assert response.status_code == 200
    assert response.json()["status"] == "success"


def test_webhook_pr_invalid_timestamp_types():
    payload = {
        "action": "closed",
        "pull_request": {
            "merged": True,
            "created_at_timestamp": "invalid_date",
            "closed_at_timestamp": "invalid_date",
            "title": "Clean code styling",
        },
    }
    response = signed_post(payload, "pull_request")
    assert response.status_code == 200
    assert response.json()["status"] == "success"

