"""Generated W4P deploy workflow: structure, opt-in, and the upload script against a mock Cloudflare API.

The upload script runs under Node when it is installed. Its asset hashes must match the Python
adapter, so a site deployed from CI gets the same version names and manifest as one deployed from Core.
"""

import json
import os
import shutil
import subprocess
import tempfile
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path

import pytest
import yaml

from src.domain.hosting.adapters.w4p_static import asset_hash, build_manifest, collect_static_files
from src.domain.hosting.deployment_targets import generate_deploy_workflow, generate_w4p_deploy_workflow
from src.domain.hosting.w4p_workflow import W4P_UPLOAD_SCRIPT

CORE = "https://api.example.test"
AUD = "p2hs-w4p-activate"


def _wf(project_type="nuxt_app", ref="pabc12345"):
    return generate_w4p_deploy_workflow(
        project_type, ref, w4p_namespace="p2hs-production", compatibility_date="2026-03-25",
        core_api_url=CORE, activation_audience=AUD,
    )


def _steps(wf):
    return yaml.safe_load(wf)["jobs"]["deploy"]["steps"]


def test_w4p_workflow_never_runs_pages_deploy_and_requests_an_oidc_token():
    wf = _wf()
    assert "pages deploy" not in wf
    assert "NITRO_PRESET=cloudflare_pages npx nuxt generate" in wf
    assert "W4P_STATIC_DIR=.output/public" in wf
    assert "W4P_STATIC_DIR=dist" in wf
    assert "cp \"$W4P_STATIC_DIR/200.html\" \"$W4P_STATIC_DIR/index.html\"" in wf
    assert "node-version: '24'" in wf
    assert "STATIC_DIR: ${{ env.W4P_STATIC_DIR || '.output/public' }}" in wf
    doc = yaml.safe_load(wf)
    assert doc["jobs"]["deploy"]["permissions"]["id-token"] == "write"
    upload = next(s for s in _steps(wf) if s.get("id") == "upload")
    assert "node --input-type=module" in upload["run"]
    activate = next(s for s in _steps(wf) if s.get("name", "").startswith("Activate"))
    assert "ACTIONS_ID_TOKEN_REQUEST_TOKEN" in activate["run"]
    assert activate["env"]["ACTIVATION_AUDIENCE"] == AUD
    assert activate["env"]["CORE_API_URL"] == CORE
    assert activate["env"]["PROJECT_REF"] == "pabc12345"
    assert activate["env"]["VERSION_SCRIPT"] == "${{ steps.upload.outputs.version_script }}"
    assert "404|409|429|500|502|503|504" in activate["run"]


def test_live_script_follows_the_branch_like_the_core_adapter():
    upload = next(s for s in _steps(_wf()) if s.get("id") == "upload")
    assert upload["env"]["LIVE_SCRIPT"] == "p2hs-${{ github.ref_name == 'preview' && 'prev' || 'prod' }}-pabc12345"
    assert upload["env"]["W4P_NAMESPACE"] == "${{ github.ref_name == 'preview' && 'p2hs-production' || 'p2hs-production' }}"


def test_upload_script_has_no_github_expression_syntax():
    assert "${{" not in W4P_UPLOAD_SCRIPT


def test_w4p_workflow_refuses_to_generate_without_core_url_or_audience():
    with pytest.raises(ValueError):
        generate_w4p_deploy_workflow("nuxt_app", "pabc12345", w4p_namespace="p2hs-production",
                                     compatibility_date="2026-03-25", core_api_url="", activation_audience=AUD)
    with pytest.raises(ValueError):
        generate_w4p_deploy_workflow("nuxt_app", "pabc12345", w4p_namespace="p2hs-production",
                                     compatibility_date="2026-03-25", core_api_url=CORE, activation_audience="")


def test_existing_pages_workflow_is_unchanged_by_default():
    wf = generate_deploy_workflow("nuxt_app", "pabc12345")
    assert "pages deploy" in wf
    assert "id-token" not in wf


def _site(root: Path) -> Path:
    site = root / "site"
    (site / "about").mkdir(parents=True)
    (site / "index.html").write_text("<h1>hi</h1>", encoding="utf-8")
    (site / "about" / "index.html").write_text("<h1>about</h1>", encoding="utf-8")
    (site / "style.css").write_text("body{}", encoding="utf-8")
    (site / ".env").write_text("SECRET=1", encoding="utf-8")
    return site


@pytest.mark.skipif(shutil.which("node") is None, reason="node not installed")
def test_upload_script_matches_the_python_adapter_against_a_mock_cloudflare():
    seen = []
    state = {}

    class Handler(BaseHTTPRequestHandler):
        def log_message(self, *a):
            pass

        def _read(self):
            return self.rfile.read(int(self.headers.get("Content-Length") or 0))

        def _send(self, obj, code=200):
            data = json.dumps(obj).encode()
            self.send_response(code)
            self.send_header("Content-Type", "application/json")
            self.send_header("Content-Length", str(len(data)))
            self.end_headers()
            self.wfile.write(data)

        def do_POST(self):
            body = self._read()
            seen.append(("POST", self.path, self.headers.get("Authorization"), body))
            if self.path.endswith("/assets-upload-session"):
                hashes = state["hashes"]
                self._send({"success": True, "errors": [], "result": {"jwt": "SESSION", "buckets": [hashes[:1], hashes[1:]]}})
            elif "/workers/assets/upload" in self.path:
                self._send({"success": True, "errors": [], "result": {"jwt": "COMPLETION"}})
            else:
                self._send({"success": False, "errors": [{"message": "unexpected"}]}, 404)

        def do_PUT(self):
            body = self._read()
            seen.append(("PUT", self.path, self.headers.get("Authorization"), body))
            self._send({"success": True, "errors": [], "result": {}})

    with tempfile.TemporaryDirectory() as tmp:
        root = Path(tmp)
        site = _site(root)
        py_files = collect_static_files(site)
        manifest_py, by_hash_py = build_manifest(py_files)
        state["hashes"] = sorted(by_hash_py)

        server = HTTPServer(("127.0.0.1", 0), Handler)
        threading.Thread(target=server.serve_forever, daemon=True).start()
        out = root / "github_output.txt"
        env = dict(os.environ)
        env.update({
            "CF_API_BASE": f"http://127.0.0.1:{server.server_address[1]}/client/v4",
            "CF_ACCOUNT_ID": "acct1",
            "CF_API_TOKEN": "TOKEN",
            "W4P_NAMESPACE": "p2hs-production",
            "LIVE_SCRIPT": "p2hs-prod-pabc12345",
            "GIT_SHA": "abcdef0123456789",
            "STATIC_DIR": str(site),
            "COMPAT_DATE": "2026-03-25",
            "GITHUB_OUTPUT": str(out),
        })
        try:
            res = subprocess.run(["node", "--input-type=module"], input=W4P_UPLOAD_SCRIPT, env=env,
                                 capture_output=True, text=True, timeout=60)
        finally:
            server.shutdown()

        assert res.returncode == 0, res.stderr
        assert out.read_text(encoding="utf-8").strip() == "version_script=p2hs-prod-pabc12345-vabcdef01"

    session = next(s for s in seen if s[1].endswith("/assets-upload-session"))
    assert json.loads(session[3])["manifest"] == manifest_py
    assert manifest_py["/index.html"]["hash"] == asset_hash("/index.html", b"<h1>hi</h1>")

    batches = [s for s in seen if "/workers/assets/upload" in s[1]]
    assert len(batches) == 2
    assert all(b[2] == "Bearer SESSION" for b in batches)
    uploaded = {h for h in by_hash_py for b in batches if f'name="{h}"'.encode() in b[3]}
    assert uploaded == set(by_hash_py)

    put = next(s for s in seen if s[0] == "PUT")
    assert put[1].endswith("/scripts/p2hs-prod-pabc12345-vabcdef01")
    assert put[2] == "Bearer TOKEN"
    metadata = json.loads(put[3].split(b"\r\n\r\n", 2)[1].split(b"\r\n--")[0])
    assert metadata["main_module"] == "worker.js"
    assert metadata["assets"]["jwt"] == "COMPLETION"
    assert metadata["bindings"] == [{"name": "ASSETS", "type": "assets"}]
    assert metadata["compatibility_date"] == "2026-03-25"


def _cfg(cutoff="2026-10-08T00:00:00+00:00"):
    from src.config import Settings

    return Settings(
        hosting_provider="cloudflare_pages",
        hosting_platform_projects_created_after=cutoff,
        api_url=CORE,
        w4p_activation_audience=AUD,
        cloudflare_worker_compatibility_date="2026-03-25",
    )


def test_new_static_project_after_cutoff_gets_the_w4p_workflow():
    from datetime import datetime, timezone

    from src.domain.hosting.deployment_targets import deploy_workflow_for_project

    created = datetime(2026, 10, 9, tzinfo=timezone.utc)
    wf = deploy_workflow_for_project("nuxt_app", "pabc12345", created, _cfg())
    assert "pages deploy" not in wf
    assert "id-token: write" in wf


def test_existing_project_keeps_the_pages_workflow():
    from datetime import datetime, timezone

    from src.domain.hosting.deployment_targets import deploy_workflow_for_project

    created = datetime(2026, 9, 1, tzinfo=timezone.utc)
    assert deploy_workflow_for_project("nuxt_app", "pabc12345", created, _cfg()) == generate_deploy_workflow("nuxt_app", "pabc12345")


def test_shopify_app_after_cutoff_is_not_moved_to_w4p():
    from datetime import datetime, timezone

    from src.domain.hosting.deployment_targets import is_w4p_project

    created = datetime(2026, 10, 9, tzinfo=timezone.utc)
    assert is_w4p_project("shopify_app", created, _cfg()) is False


def test_no_cutoff_means_no_w4p_anywhere():
    from datetime import datetime, timezone

    from src.domain.hosting.deployment_targets import is_w4p_project

    assert is_w4p_project("nuxt_app", datetime(2026, 10, 9, tzinfo=timezone.utc), _cfg(cutoff="")) is False


def test_codegen_run_workflow_has_no_pages_deploy_for_w4p_projects():
    from src.domain.codegen.actions_pipeline import build_codegen_actions_workflow

    assert "pages deploy" in build_codegen_actions_workflow("nuxt_app", "pabc12345")
    assert "pages deploy" not in build_codegen_actions_workflow("nuxt_app", "pabc12345", w4p=True)
