from conftest import register
import sqlite3

from fastapi.testclient import TestClient

from polyform_backend.api import create_app


def test_release_round_trip_and_duplicate_rejected(api,release):
    assert api.get(f"/api/v1/releases/{release['id']}").json() == release
    assert api.post("/api/v1/releases",json={"application":"zipdiv","version":"1.0.0","spec_functions":[
        {"name":"x","implementations":[{"id":"x","name":"x"}]}]}).status_code == 409


def test_published_release_is_available_in_catalog_with_source_metadata(api):
    digest="a"*64
    body={"application":"sample","version":"1.2.3","repository_url":"https://github.com/example/sample",
          "source_commit":"abc123","spec_path":"specs/sample.toml","artifact_url":"https://example.test/sample",
          "artifact_name":"sample","artifact_sha256":digest,"evidence_sha256":"b"*64,
          "signing_public_key":"public","signature":"signature","spec_functions":[
          {"name":"work","implementations":[{"id":"work.first","name":"first","provenance":{
            "source_url":"https://github.com/example/sample/blob/abc123/src/lib.rs"}}]}]}
    created=api.post("/api/v1/releases",json=body)
    assert created.status_code == 201
    catalog=api.get("/api/v1/catalog/sample").json()
    assert catalog["artifact_sha256"] == digest
    assert catalog["repository_url"] == body["repository_url"]
    assert api.get("/api/v1/catalog/sample",params={"version":"1.2.3"}).json()["id"] == catalog["id"]
    assert any(item["application"] == "sample" for item in api.get("/api/v1/catalog").json())
    stats=api.get("/api/v1/stats/implementations",params={"release_id":catalog["id"]}).json()
    assert stats[0]["source_url"].endswith("src/lib.rs")


def test_admin_token_protects_release_publication(api,monkeypatch):
    monkeypatch.setenv("POLYFORM_ADMIN_TOKEN","test-secret")
    body={"application":"protected","version":"1.0.0","spec_functions":[
      {"name":"work","implementations":[{"id":"work.safe","name":"safe"}]}]}
    assert api.post("/api/v1/releases",json=body).status_code == 401
    response=api.post("/api/v1/releases",json=body,headers={"X-Polyform-Admin-Token":"test-secret"})
    assert response.status_code == 201


def test_pseudonymous_registration_is_idempotent(api,release):
    a=register(api,release,1); b=register(api,release,1)
    assert a["client_id"] == b["client_id"]
    assert a["client_id"].startswith("pc_")
    assert "install" not in str(api.get("/api/v1/dashboard/summary").json())


def test_balanced_assignment_and_random_are_valid(api,release):
    clients=[register(api,release,n) for n in range(20)]
    for spec in ("parse_header","normalize_path"):
        counts={impl:sum(c["implementations"][spec]==impl for c in clients) for impl in {c["implementations"][spec] for c in clients}}
        assert max(counts.values())-min(counts.values()) <= 1
    random_comp=register(api,release,999,"random")
    assert set(random_comp["implementations"]) == {"parse_header","normalize_path"}


def test_quarantine_recomposes_affected_clients(api,release):
    clients=[register(api,release,n) for n in range(8)]
    affected=[c for c in clients if "header_bad" in c["implementations"].values()]
    response=api.put("/api/v1/implementations/header_bad/status",json={"status":"quarantined","reason":"high relative risk"})
    assert response.status_code == 200
    assert response.json()["affected_clients"] == len(affected)
    for old in affected:
        new=api.get(f"/api/v1/clients/{old['client_id']}/composition").json()
        assert new["revision"] == 2
        assert "header_bad" not in new["implementations"].values()
    assert api.put("/api/v1/implementations/header_a/status",json={"status":"quarantined"}).status_code == 409
    restored=api.put("/api/v1/implementations/header_bad/status",json={"status":"active","reason":"operator review"})
    assert restored.status_code == 200 and restored.json()["affected_clients"] == 0
    future=register(api,release,1000)
    assert future["implementations"]["parse_header"] == "header_bad"


def test_installation_identity_is_scoped_to_release(api,release):
    second=api.post("/api/v1/releases",json={"application":"zipdiv","version":"2.0.0","spec_functions":[
      {"name":"parse_header","implementations":[{"id":"header_v2","name":"v2"}]}]}).json()
    first=register(api,release,77)
    response=api.post("/api/v1/clients",json={"release_id":second["id"],"installation_id":"install-balanced-77","strategy":"balanced"})
    assert response.status_code == 201
    upgraded=response.json()
    assert upgraded["release_id"] == str(second["id"])
    assert upgraded["client_id"] != first["client_id"]


def test_implementation_ids_are_release_scoped_and_quarantine_is_unambiguous(api,release):
    second_response=api.post("/api/v1/releases",json={"application":"zipdiv","version":"2.0.0","spec_functions":[
      {"name":"parse_header","implementations":[{"id":"header_a","name":"v2 a"},{"id":"header_bad","name":"v2 bad"}]},
      {"name":"normalize_path","implementations":[{"id":"path_a","name":"v2 a"},{"id":"path_b","name":"v2 b"}]}
    ]})
    assert second_response.status_code == 201
    second=second_response.json()
    first_client=register(api,release,501)
    second_clients=[api.post("/api/v1/clients",json={
      "release_id":second["id"],"installation_id":f"v2-shared-{n}","strategy":"balanced"
    }).json() for n in range(4)]
    affected=sum(c["implementations"]["parse_header"]=="header_bad" for c in second_clients)

    ambiguous=api.put("/api/v1/implementations/header_bad/status",json={"status":"quarantined"})
    assert ambiguous.status_code == 409
    scoped=api.put("/api/v1/implementations/header_bad/status",params={"release_id":second["id"]},
                   json={"status":"quarantined","reason":"v2 only"})
    assert scoped.status_code == 200 and scoped.json()["affected_clients"] == affected
    assert scoped.json()["release_id"] == second["id"]
    first_release=api.get(f"/api/v1/releases/{release['id']}").json()
    second_release=api.get(f"/api/v1/releases/{second['id']}").json()
    first_bad=next(i for s in first_release["spec_functions"] for i in s["implementations"] if i["id"]=="header_bad")
    second_bad=next(i for s in second_release["spec_functions"] for i in s["implementations"] if i["id"]=="header_bad")
    assert first_bad["status"] == "active" and second_bad["status"] == "quarantined"
    assert api.get(f"/api/v1/clients/{first_client['client_id']}/composition").json()["revision"] == 1


def test_legacy_global_id_database_migrates_in_place(tmp_path):
    path=tmp_path/"legacy.db"
    with sqlite3.connect(path) as con:
        con.executescript("""
        CREATE TABLE releases(id INTEGER PRIMARY KEY,application TEXT,version TEXT,created_at TEXT,UNIQUE(application,version));
        CREATE TABLE spec_functions(id INTEGER PRIMARY KEY,release_id INTEGER,name TEXT,UNIQUE(release_id,name));
        CREATE TABLE implementations(id TEXT PRIMARY KEY,spec_function_id INTEGER,name TEXT,provenance TEXT,status TEXT,status_reason TEXT,status_changed_at TEXT);
        CREATE TABLE composition_items(composition_id INTEGER,spec_function_id INTEGER,implementation_id TEXT,PRIMARY KEY(composition_id,spec_function_id));
        CREATE TABLE executions(id INTEGER PRIMARY KEY,client_id TEXT,created_at TEXT);
        CREATE TABLE status_changes(id INTEGER PRIMARY KEY,implementation_id TEXT,old_status TEXT,new_status TEXT,reason TEXT,affected_clients INTEGER,execution_cutoff INTEGER,changed_at TEXT);
        INSERT INTO releases VALUES(1,'zipdiv','1.0.0','2026-01-01');
        INSERT INTO spec_functions VALUES(1,1,'parse_header');
        INSERT INTO implementations VALUES('header_a',1,'legacy','{}','active','',NULL);
        INSERT INTO status_changes VALUES(1,'header_a','active','quarantined','legacy audit',3,7,'2026-01-02');
        """)
    with TestClient(create_app(str(path))) as migrated:
        release=migrated.get("/api/v1/releases/1").json()
        assert release["spec_functions"][0]["implementations"][0]["id"] == "header_a"
        response=migrated.post("/api/v1/releases",json={"application":"zipdiv","version":"2.0.0","spec_functions":[
          {"name":"parse_header","implementations":[{"id":"header_a","name":"reused"}]}
        ]})
        assert response.status_code == 201
    with sqlite3.connect(path) as con:
        audit=con.execute("""SELECT i.id,sc.reason,sc.execution_cutoff FROM status_changes sc
          JOIN implementations i ON i.pk=sc.implementation_pk""").fetchone()
        assert audit == ("header_a","legacy audit",7)
