// Acceptance for P2.1 — the domain boundary validator. Constructs valid
// App/Release/Artifact/Channel and asserts they are accepted, then mutates
// each into one invalid form and asserts it is rejected with the SPECIFIC
// typed ValidationErr (not merely "some failure"). Prints a pass/fail line
// per case and exits non-zero if any case behaves unexpectedly.
#import "modules/std.sx";
#import "../src/domain/platform.sx";
#import "../src/domain/app.sx";
#import "../src/domain/release.sx";
#import "../src/domain/artifact.sx";
#import "../src/domain/channel.sx";
#import "../src/domain/validate.sx";

// ── Valid fixtures ──────────────────────────────────────────────────────

valid_app :: () -> App {
    a : App = .{
        id = "app_01",
        slug = "acme-app",
        display_name = "Acme App",
        owner = "user_01",
        visibility = .public,
        created_at = 1700000000,
        updated_at = 1700000000,
    };
    a.bundle_ids.append(BundleId.{ platform = .ios, value = "co.acme.app" });
    return a;
}

valid_release :: () -> Release {
    return Release.{
        id = "rel_01",
        app_id = "app_01",
        version = "1.2.3-beta.1",
        build = 42,
        channel = "stable",
        notes = "first cut",
        created_by = "user_01",
        created_at = 1700000000,
        published_at = 1700000200,
    };
}

valid_artifact :: () -> Artifact {
    return Artifact.{
        id = "art_01",
        app_id = "app_01",
        release_id = "rel_01",
        platform = .android_apk,
        filename = "acme.apk",
        content_type = "application/vnd.android.package-archive",
        size_bytes = 10485760,
        sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
        storage_key = "apps/app_01/rel_01/acme.apk",
        metadata = "{\"min_os\":\"14\"}",
        validation_status = .valid,
    };
}

valid_channel :: () -> Channel {
    return Channel.{
        app_id = "app_01",
        name = "stable",
        current_release_id = "rel_01",
        policy = .percentage,
        rollout_percent = 25,
    };
}

// ── Cases ───────────────────────────────────────────────────────────────

check_accepts_app :: () -> bool {
    a := valid_app();
    ok := true;
    validate_app(a) catch { ok = false; };
    return ok;
}

check_accepts_release :: () -> bool {
    r := valid_release();
    ok := true;
    validate_release(r) catch { ok = false; };
    return ok;
}

check_accepts_artifact :: () -> bool {
    a := valid_artifact();
    ok := true;
    validate_artifact(a) catch { ok = false; };
    return ok;
}

check_accepts_channel :: () -> bool {
    c := valid_channel();
    ok := true;
    validate_channel(c) catch { ok = false; };
    return ok;
}

check_accepts_platform :: () -> bool {
    p, e := parse_platform("android_apk");
    ok := false;
    if !e { ok = (p == .android_apk); }
    return ok;
}

check_rejects_bad_slug :: () -> bool {
    a := valid_app();
    a.slug = "Bad_Slug";              // uppercase + underscore
    matched := false;
    validate_app(a) catch (e) { matched = (e == error.BadSlug); };
    return matched;
}

check_rejects_empty_version :: () -> bool {
    r := valid_release();
    r.version = "";
    matched := false;
    validate_release(r) catch (e) { matched = (e == error.EmptyVersion); };
    return matched;
}

check_rejects_bad_version :: () -> bool {
    r := valid_release();
    r.version = "1.2";                // missing PATCH component
    matched := false;
    validate_release(r) catch (e) { matched = (e == error.BadVersion); };
    return matched;
}

check_rejects_unknown_platform :: () -> bool {
    _, e := parse_platform("plan9");
    return e == error.UnknownPlatform;
}

check_rejects_bad_channel :: () -> bool {
    c := valid_channel();
    c.name = "Bad Channel";           // space + uppercase
    matched := false;
    validate_channel(c) catch (e) { matched = (e == error.BadChannelName); };
    return matched;
}

check_rejects_empty_content_type :: () -> bool {
    a := valid_artifact();
    a.content_type = "";              // required string cleared
    matched := false;
    validate_artifact(a) catch (e) { matched = (e == error.MissingField); };
    return matched;
}

check_rejects_bad_size :: () -> bool {
    a := valid_artifact();
    a.size_bytes = -1;                // a content-addressed artifact has positive bytes
    matched := false;
    validate_artifact(a) catch (e) { matched = (e == error.BadSize); };
    return matched;
}

check_rejects_bad_digest :: () -> bool {
    a := valid_artifact();
    a.sha256 = "not-a-sha";           // not 64 lowercase-hex chars
    matched := false;
    validate_artifact(a) catch (e) { matched = (e == error.BadDigest); };
    return matched;
}

// Contract pins: read back the renamed Release.published_at and the new
// Artifact.metadata. These references fail to compile against the pre-change
// structs (Release.updated_at / no Artifact.metadata).
check_release_published_at :: () -> bool {
    r := valid_release();
    return r.published_at == 1700000200;
}

check_artifact_metadata :: () -> bool {
    a := valid_artifact();
    return a.metadata == "{\"min_os\":\"14\"}";
}

run_case :: (label: string, ok: bool) -> i32 {
    if ok { print("  PASS  {}\n", label); return 0; }
    print("  FAIL  {}\n", label);
    return 1;
}

main :: () -> i32 {
    failures : i32 = 0;
    failures += run_case("accept: valid app",                       check_accepts_app());
    failures += run_case("accept: valid release",                   check_accepts_release());
    failures += run_case("accept: valid artifact",                  check_accepts_artifact());
    failures += run_case("accept: valid channel",                   check_accepts_channel());
    failures += run_case("accept: platform 'android_apk'",          check_accepts_platform());
    failures += run_case("reject: bad slug -> BadSlug",             check_rejects_bad_slug());
    failures += run_case("reject: empty version -> EmptyVersion",   check_rejects_empty_version());
    failures += run_case("reject: bad version -> BadVersion",       check_rejects_bad_version());
    failures += run_case("reject: unknown platform -> UnknownPlatform", check_rejects_unknown_platform());
    failures += run_case("reject: bad channel -> BadChannelName",   check_rejects_bad_channel());
    failures += run_case("reject: empty content_type -> MissingField", check_rejects_empty_content_type());
    failures += run_case("reject: non-positive size_bytes -> BadSize", check_rejects_bad_size());
    failures += run_case("reject: malformed sha256 -> BadDigest",   check_rejects_bad_digest());
    failures += run_case("contract: Release.published_at readback",  check_release_published_at());
    failures += run_case("contract: Artifact.metadata readback",     check_artifact_metadata());

    print("------------------------------------------------\n");
    if failures == 0 {
        print("domain_validate: ALL CASES PASS\n");
        return 0;
    }
    print("domain_validate: {} CASE(S) FAILED\n", failures);
    return 1;
}
