import sys
root = r'C:\Users\decid\Documents\projects\spt-core\.worktrees\w6-divulge' + '\\'

def patch(rel, pairs):
    p = root + rel
    b = open(p, 'rb').read()
    crlf = b'\r\n' in b
    s = b.decode().replace('\r\n', '\n')
    for a, n in pairs:
        c = s.count(a)
        if c != 1:
            sys.exit(f'{rel}: {c} hits for {a!r}')
        s = s.replace(a, n)
    if crlf:
        s = s.replace('\n', '\r\n')
    open(p, 'wb').write(s.encode())

patch('crates/spt-daemon/src/release.rs', [(
'''impl VerifyPolicy {
    /// Assemble the production policy''',
'''/// What `identity/release-keys.json` does to the compiled-in anchor, read for
/// REPORTING only (releases#64): the key ids it adds, the ids it revokes, and
/// the channel it pins (`stable` when it names none). `Unreadable` = the file
/// exists but does not parse — the policy then falls back to the built-in set,
/// yet the file's presence still means someone meant to override, so it is
/// reported rather than hidden. `None` from [`release_keys_override`] = no file.
// [impl->REQ-UPDATE-STATUS-TRUST-ANCHOR]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(tag = "state", rename_all = "kebab-case")]
pub enum ReleaseKeysOverride {
    Parsed {
        keys: Vec<String>,
        revoked: Vec<String>,
        channel: String,
    },
    Unreadable,
}

/// Read `release-keys.json` at `path` for the status surface. Read-only: it
/// never writes, prunes or repairs the file.
// [impl->REQ-UPDATE-STATUS-TRUST-ANCHOR]
pub fn release_keys_override(path: &std::path::Path) -> Option<ReleaseKeysOverride> {
    let raw = match std::fs::read_to_string(path) {
        Ok(raw) => raw,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => return None,
        Err(_) => return Some(ReleaseKeysOverride::Unreadable),
    };
    Some(match serde_json::from_str::<ReleaseKeysFile>(&raw) {
        Ok(file) => ReleaseKeysOverride::Parsed {
            keys: file.keys.into_keys().collect(),
            revoked: file.revoked.into_iter().collect(),
            channel: file.channel.unwrap_or_else(|| DEFAULT_CHANNEL.to_string()),
        },
        Err(_) => ReleaseKeysOverride::Unreadable,
    })
}

impl VerifyPolicy {
    /// Assemble the production policy''')])

patch('crates/spt/src/cli.rs', [
(
'''    let last = cache.last_outcome();
    let sources = cache.sources();
    let known = source_known_nodes(&sources);
    if json {
        println!(
            "{}",
            serde_json::json!({
                "staged_version": staged_version,
                "staged_product_version": staged_label,
                "staged_channel": channel,
                "applied_version": applied,
                "last_outcome": last,
                "sources": sources.sources,
            })
        );
        return 0;
    }''',
'''    let last = cache.last_outcome();
    let sources = cache.sources();
    let known = source_known_nodes(&sources);
    // [impl->REQ-UPDATE-STATUS-TRUST-ANCHOR] read-only, like the rest of the verb.
    let keys_path = perch::identity_dir().join("release-keys.json");
    let anchor = spt_daemon::release_keys_override(&keys_path);
    if json {
        let mut out = serde_json::json!({
            "staged_version": staged_version,
            "staged_product_version": staged_label,
            "staged_channel": channel,
            "applied_version": applied,
            "last_outcome": last,
            "sources": sources.sources,
        });
        // Present only when overridden, so a node with no file emits exactly
        // the object it emitted before.
        // [impl->REQ-UPDATE-STATUS-TRUST-ANCHOR]
        if let Some(anchor) = &anchor {
            out["trust_anchor_override"] = serde_json::json!({
                "path": keys_path.to_string_lossy(),
                "override": anchor,
            });
        }
        println!("{out}");
        return 0;
    }'''),
(
'''    println!("last: {}", last.as_deref().unwrap_or("none recorded"));
    println!("{}", sources_summary_line(&sources, &known));
    0
}''',
'''    println!("last: {}", last.as_deref().unwrap_or("none recorded"));
    println!("{}", sources_summary_line(&sources, &known));
    // [impl->REQ-UPDATE-STATUS-TRUST-ANCHOR]
    for line in trust_anchor_lines(anchor.as_ref(), &keys_path) {
        println!("{line}");
    }
    0
}

/// The trust-anchor lines of `spt update status` (releases#64) — pure, unit-
/// tested. Nothing without an override file; otherwise the OVERRIDDEN line
/// naming every added key id (and every revoked one) and the pinned channel,
/// then the cleanup hint. It declares NO expiry: the file has none to read.
// [impl->REQ-UPDATE-STATUS-TRUST-ANCHOR]
fn trust_anchor_lines(
    anchor: Option<&spt_daemon::ReleaseKeysOverride>,
    keys_path: &std::path::Path,
) -> Vec<String> {
    let hint = format!(
        "  to return to the built-in anchor, delete {}",
        keys_path.display()
    );
    match anchor {
        None => Vec::new(),
        Some(spt_daemon::ReleaseKeysOverride::Parsed {
            keys,
            revoked,
            channel,
        }) => {
            let keys = if keys.is_empty() {
                "none".to_string()
            } else {
                keys.join(",")
            };
            let revoked = if revoked.is_empty() {
                String::new()
            } else {
                format!(", revoked {}", revoked.join(","))
            };
            vec![
                format!(
                    "trust anchor OVERRIDDEN (identity/release-keys.json, key {keys}{revoked}, \\
                     channel {channel})"
                ),
                hint,
            ]
        }
        Some(spt_daemon::ReleaseKeysOverride::Unreadable) => vec![
            "trust anchor OVERRIDDEN (identity/release-keys.json present but unreadable — \\
             the built-in anchor applies)"
                .to_string(),
            hint,
        ],
    }
}'''),
])

patch('crates/spt-daemon/src/lib.rs', [(
'''pub use relcache::{''',
'''pub use release::{release_keys_override, ReleaseKeysOverride};
pub use relcache::{''')])
print('ok')
