use std::collections::BTreeSet;
use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};

use base64::Engine;
use ed25519_dalek::pkcs8::{
    spki::der::pem::LineEnding, DecodePrivateKey, DecodePublicKey, EncodePrivateKey,
    EncodePublicKey,
};
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use time::format_description::well_known::Rfc3339;
use url::Url;

use crate::package::load_skills_config;

pub(crate) const SIGNER_REGISTRY_URL_ENV: &str = "HARN_SKILL_SIGNER_REGISTRY_URL";
const SIG_SCHEMA: &str = "harn-skill-sig/v2";

#[derive(Debug, Clone)]
pub(crate) struct GeneratedKeypair {
    pub private_key_path: PathBuf,
    pub public_key_path: PathBuf,
    pub fingerprint: String,
}

#[derive(Debug, Clone)]
pub(crate) struct SignedSkill {
    pub signature_path: PathBuf,
    pub signer_fingerprint: String,
    pub skill_sha256: String,
}

#[derive(Debug, Clone)]
pub(crate) struct EndorsedSkill {
    pub signature_path: PathBuf,
    pub endorser_fingerprint: String,
    pub skill_sha256: String,
}

#[derive(Debug, Clone, Default)]
pub(crate) struct VerifyOptions {
    pub registry_url: Option<String>,
    pub allowed_signers: Vec<String>,
    pub allowed_endorsers: Vec<String>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub(crate) struct TrustPolicy {
    #[serde(default, alias = "registry_url")]
    pub signer_registry_url: Option<String>,
    #[serde(default)]
    pub trusted_signers: Vec<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum VerificationStatus {
    Verified,
    MissingSignature,
    InvalidSignature,
    MissingSigner,
    UntrustedSigner,
    MissingEndorsement,
}

impl VerificationStatus {
    pub(crate) fn as_str(self) -> &'static str {
        match self {
            VerificationStatus::Verified => "verified",
            VerificationStatus::MissingSignature => "missing_signature",
            VerificationStatus::InvalidSignature => "invalid_signature",
            VerificationStatus::MissingSigner => "missing_signer",
            VerificationStatus::UntrustedSigner => "untrusted_signer",
            VerificationStatus::MissingEndorsement => "missing_endorsement",
        }
    }
}

#[derive(Debug, Clone)]
pub(crate) struct EndorsementReport {
    pub endorser_fingerprint: String,
    pub signed_at: String,
    pub trusted: bool,
    pub status: VerificationStatus,
    pub error: Option<String>,
}

#[derive(Debug, Clone)]
pub(crate) struct VerificationReport {
    pub skill_path: PathBuf,
    pub signature_path: PathBuf,
    pub skill_sha256: String,
    pub signer_fingerprint: Option<String>,
    pub signed_at: Option<String>,
    pub endorsements: Vec<EndorsementReport>,
    pub signed: bool,
    pub trusted: bool,
    pub status: VerificationStatus,
    pub error: Option<String>,
}

impl VerificationReport {
    pub(crate) fn is_verified(&self) -> bool {
        self.status == VerificationStatus::Verified
    }

    pub(crate) fn human_summary(&self) -> String {
        match &self.error {
            Some(error) => error.clone(),
            None => match self.status {
                VerificationStatus::Verified => format!(
                    "{} verified by {}",
                    self.skill_path.display(),
                    self.signer_fingerprint.clone().unwrap_or_default()
                ),
                VerificationStatus::MissingSignature => format!(
                    "{} is missing {}",
                    self.skill_path.display(),
                    self.signature_path.display()
                ),
                VerificationStatus::InvalidSignature => {
                    format!("{} has an invalid signature", self.skill_path.display())
                }
                VerificationStatus::MissingSigner => format!(
                    "{} was signed by {}, but that signer is not installed locally and no registry resolved it",
                    self.skill_path.display(),
                    self.signer_fingerprint.clone().unwrap_or_default()
                ),
                VerificationStatus::UntrustedSigner => format!(
                    "{} was signed by {}, but that signer is not trusted for this skill",
                    self.skill_path.display(),
                    self.signer_fingerprint.clone().unwrap_or_default()
                ),
                VerificationStatus::MissingEndorsement => format!(
                    "{} is missing at least one trusted endorsement signature",
                    self.skill_path.display()
                ),
            },
        }
    }
}

#[derive(Debug, Clone)]
pub(crate) struct TrustedSignerRecord {
    pub fingerprint: String,
    pub path: PathBuf,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct SkillSignatureEnvelope {
    pub schema: String,
    pub signed_at: String,
    pub signer_fingerprint: String,
    pub ed25519_sig_base64: String,
    pub skill_sha256: String,
    #[serde(default)]
    pub endorsements: Vec<SkillSignatureEndorsement>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct SkillSignatureEndorsement {
    pub signed_at: String,
    pub endorser_fingerprint: String,
    pub ed25519_sig_base64: String,
}

pub(crate) fn generate_keypair(out: impl AsRef<Path>) -> Result<GeneratedKeypair, String> {
    let private_key_path = out.as_ref().to_path_buf();
    if let Some(parent) = private_key_path.parent() {
        fs::create_dir_all(parent).map_err(|error| {
            format!(
                "failed to create private-key directory {}: {error}",
                parent.display()
            )
        })?;
    }
    let public_key_path = append_suffix(&private_key_path, ".pub");

    let seed: [u8; 32] = rand::random();
    let signing_key = SigningKey::from_bytes(&seed);
    let verifying_key = signing_key.verifying_key();
    let private_pem = signing_key
        .to_pkcs8_pem(LineEnding::LF)
        .map_err(|error| format!("failed to encode private key as PEM: {error}"))?;
    let public_pem = verifying_key
        .to_public_key_pem(LineEnding::LF)
        .map_err(|error| format!("failed to encode public key as PEM: {error}"))?;

    fs::write(&private_key_path, private_pem.as_bytes()).map_err(|error| {
        format!(
            "failed to write private key {}: {error}",
            private_key_path.display()
        )
    })?;
    fs::write(&public_key_path, public_pem.as_bytes()).map_err(|error| {
        format!(
            "failed to write public key {}: {error}",
            public_key_path.display()
        )
    })?;

    Ok(GeneratedKeypair {
        private_key_path,
        public_key_path,
        fingerprint: fingerprint_for_key(&verifying_key),
    })
}

pub(crate) fn sign_skill(
    skill_path: impl AsRef<Path>,
    private_key_path: impl AsRef<Path>,
) -> Result<SignedSkill, String> {
    let skill_path = skill_path.as_ref();
    let private_key_path = private_key_path.as_ref();
    let skill_bytes = fs::read(skill_path)
        .map_err(|error| format!("failed to read {}: {error}", skill_path.display()))?;
    let signing_key = load_ed25519_signing_key(private_key_path)?;
    let signature = signing_key.sign(&skill_bytes);
    let signer_fingerprint = fingerprint_for_key(&signing_key.verifying_key());
    let skill_sha256 = sha256_hex(&skill_bytes);
    let signed_at = time::OffsetDateTime::now_utc()
        .format(&Rfc3339)
        .map_err(|error| format!("failed to format signed_at timestamp: {error}"))?;
    let envelope = SkillSignatureEnvelope {
        schema: SIG_SCHEMA.to_string(),
        signed_at,
        signer_fingerprint: signer_fingerprint.clone(),
        ed25519_sig_base64: base64::engine::general_purpose::STANDARD.encode(signature.to_bytes()),
        skill_sha256: skill_sha256.clone(),
        endorsements: Vec::new(),
    };
    let signature_path = signature_path_for(skill_path);
    let serialized = serde_json::to_string_pretty(&envelope)
        .map_err(|error| format!("failed to serialize signature: {error}"))?;
    fs::write(&signature_path, serialized.as_bytes()).map_err(|error| {
        format!(
            "failed to write signature {}: {error}",
            signature_path.display()
        )
    })?;

    Ok(SignedSkill {
        signature_path,
        signer_fingerprint,
        skill_sha256,
    })
}

pub(crate) fn endorse_skill(
    skill_path: impl AsRef<Path>,
    private_key_path: impl AsRef<Path>,
) -> Result<EndorsedSkill, String> {
    let skill_path = skill_path.as_ref();
    let private_key_path = private_key_path.as_ref();
    let skill_bytes = fs::read(skill_path)
        .map_err(|error| format!("failed to read {}: {error}", skill_path.display()))?;
    let skill_sha256 = sha256_hex(&skill_bytes);
    let signature_path = signature_path_for(skill_path);
    let mut envelope = read_signature_envelope(&signature_path)?;
    if envelope.schema != SIG_SCHEMA {
        return Err(format!(
            "{} declares unsupported schema {}",
            signature_path.display(),
            envelope.schema
        ));
    }
    if envelope.skill_sha256 != skill_sha256 {
        return Err(format!(
            "{} does not match the current contents of {}",
            signature_path.display(),
            skill_path.display()
        ));
    }

    let signing_key = load_ed25519_signing_key(private_key_path)?;
    let endorser_fingerprint = fingerprint_for_key(&signing_key.verifying_key());
    if endorser_fingerprint == envelope.signer_fingerprint {
        return Err(
            "skill endorsements must be signed by a different key than the author signature"
                .to_string(),
        );
    }
    let signed_at = time::OffsetDateTime::now_utc()
        .format(&Rfc3339)
        .map_err(|error| format!("failed to format signed_at timestamp: {error}"))?;
    let endorsement = SkillSignatureEndorsement {
        signed_at,
        endorser_fingerprint: endorser_fingerprint.clone(),
        ed25519_sig_base64: base64::engine::general_purpose::STANDARD
            .encode(signing_key.sign(&skill_bytes).to_bytes()),
    };
    envelope
        .endorsements
        .retain(|existing| existing.endorser_fingerprint != endorser_fingerprint);
    envelope.endorsements.push(endorsement);
    envelope
        .endorsements
        .sort_by(|left, right| left.endorser_fingerprint.cmp(&right.endorser_fingerprint));
    let serialized = serde_json::to_string_pretty(&envelope)
        .map_err(|error| format!("failed to serialize signature: {error}"))?;
    fs::write(&signature_path, serialized.as_bytes()).map_err(|error| {
        format!(
            "failed to write signature {}: {error}",
            signature_path.display()
        )
    })?;

    Ok(EndorsedSkill {
        signature_path,
        endorser_fingerprint,
        skill_sha256,
    })
}

pub(crate) fn verify_skill(
    skill_path: impl AsRef<Path>,
    options: &VerifyOptions,
) -> Result<VerificationReport, String> {
    let skill_path = skill_path.as_ref();
    let skill_bytes = fs::read(skill_path)
        .map_err(|error| format!("failed to read {}: {error}", skill_path.display()))?;
    let skill_sha256 = sha256_hex(&skill_bytes);
    let signature_path = signature_path_for(skill_path);
    let allowed_signers: BTreeSet<String> = options.allowed_signers.iter().cloned().collect();
    let base_report = VerificationReport {
        skill_path: skill_path.to_path_buf(),
        signature_path: signature_path.clone(),
        skill_sha256: skill_sha256.clone(),
        signer_fingerprint: None,
        signed_at: None,
        endorsements: Vec::new(),
        signed: false,
        trusted: false,
        status: VerificationStatus::MissingSignature,
        error: None,
    };

    let signature_raw = match fs::read_to_string(&signature_path) {
        Ok(raw) => raw,
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(base_report),
        Err(error) => {
            return Err(format!(
                "failed to read signature {}: {error}",
                signature_path.display()
            ))
        }
    };
    let envelope: SkillSignatureEnvelope = match serde_json::from_str(&signature_raw) {
        Ok(envelope) => envelope,
        Err(error) => {
            return Ok(VerificationReport {
                error: Some(format!(
                    "{} is not valid {} JSON: {error}",
                    signature_path.display(),
                    SIG_SCHEMA
                )),
                status: VerificationStatus::InvalidSignature,
                ..base_report
            })
        }
    };
    if envelope.schema != SIG_SCHEMA {
        return Ok(VerificationReport {
            signer_fingerprint: Some(envelope.signer_fingerprint),
            status: VerificationStatus::InvalidSignature,
            error: Some(format!(
                "{} declares unsupported schema {}",
                signature_path.display(),
                envelope.schema
            )),
            ..base_report
        });
    }
    if envelope.skill_sha256 != skill_sha256 {
        return Ok(VerificationReport {
            signer_fingerprint: Some(envelope.signer_fingerprint),
            status: VerificationStatus::InvalidSignature,
            error: Some(format!(
                "{} does not match the current contents of {}",
                signature_path.display(),
                skill_path.display()
            )),
            ..base_report
        });
    }

    let signer_fingerprint = envelope.signer_fingerprint.clone();
    let base_report = VerificationReport {
        signer_fingerprint: Some(signer_fingerprint.clone()),
        signed_at: Some(envelope.signed_at.clone()),
        signed: true,
        ..base_report
    };

    let verifying_key =
        match resolve_verifying_key(&signer_fingerprint, options.registry_url.as_deref())? {
            Some(key) => key,
            None => {
                return Ok(VerificationReport {
                    status: VerificationStatus::MissingSigner,
                    error: Some(format!(
                        "{} was signed by {}, but {} is not present in {}",
                        skill_path.display(),
                        signer_fingerprint,
                        signer_fingerprint,
                        trusted_signers_dir()?.display()
                    )),
                    ..base_report
                })
            }
        };
    let signature_bytes = match base64::engine::general_purpose::STANDARD
        .decode(envelope.ed25519_sig_base64.as_bytes())
    {
        Ok(bytes) => bytes,
        Err(error) => {
            return Ok(VerificationReport {
                status: VerificationStatus::InvalidSignature,
                error: Some(format!("signature is not valid base64: {error}")),
                ..base_report
            })
        }
    };
    let signature = match Signature::from_slice(&signature_bytes) {
        Ok(signature) => signature,
        Err(error) => {
            return Ok(VerificationReport {
                status: VerificationStatus::InvalidSignature,
                error: Some(format!("signature is not valid Ed25519 bytes: {error}")),
                ..base_report
            })
        }
    };
    if verifying_key.verify(&skill_bytes, &signature).is_err() {
        return Ok(VerificationReport {
            status: VerificationStatus::InvalidSignature,
            error: Some(format!(
                "{} failed Ed25519 verification for {}",
                signature_path.display(),
                skill_path.display()
            )),
            ..base_report
        });
    }
    if !allowed_signers.is_empty() && !allowed_signers.contains(&signer_fingerprint) {
        return Ok(VerificationReport {
            status: VerificationStatus::UntrustedSigner,
            error: Some(format!(
                "{} was signed by {}, which is not in the skill's trusted_signers allowlist",
                skill_path.display(),
                signer_fingerprint
            )),
            ..base_report
        });
    }

    let endorsement_reports = verify_endorsements(&skill_bytes, &envelope.endorsements, options)?;
    if endorsement_reports.is_empty() {
        return Ok(VerificationReport {
            endorsements: endorsement_reports,
            status: VerificationStatus::MissingEndorsement,
            error: Some(format!(
                "{} has no endorsement signatures; add at least one with `harn skill endorse`",
                skill_path.display()
            )),
            ..base_report
        });
    }
    if let Some(failed) = endorsement_reports
        .iter()
        .find(|endorsement| endorsement.status != VerificationStatus::Verified)
    {
        return Ok(VerificationReport {
            endorsements: endorsement_reports.clone(),
            status: failed.status,
            error: failed.error.clone(),
            ..base_report
        });
    }

    Ok(VerificationReport {
        endorsements: endorsement_reports,
        trusted: true,
        status: VerificationStatus::Verified,
        ..base_report
    })
}

fn verify_endorsements(
    skill_bytes: &[u8],
    endorsements: &[SkillSignatureEndorsement],
    options: &VerifyOptions,
) -> Result<Vec<EndorsementReport>, String> {
    let allowed_endorsers: BTreeSet<String> = options.allowed_endorsers.iter().cloned().collect();
    endorsements
        .iter()
        .map(|endorsement| {
            let fingerprint = endorsement.endorser_fingerprint.clone();
            let base_report = EndorsementReport {
                endorser_fingerprint: fingerprint.clone(),
                signed_at: endorsement.signed_at.clone(),
                trusted: false,
                status: VerificationStatus::InvalidSignature,
                error: None,
            };
            let Some(verifying_key) =
                resolve_verifying_key(&fingerprint, options.registry_url.as_deref())?
            else {
                return Ok(EndorsementReport {
                    status: VerificationStatus::MissingSigner,
                    error: Some(format!(
                        "endorsement signer {fingerprint} is not installed locally and no registry resolved it"
                    )),
                    ..base_report
                });
            };
            if !allowed_endorsers.is_empty() && !allowed_endorsers.contains(&fingerprint) {
                return Ok(EndorsementReport {
                    status: VerificationStatus::UntrustedSigner,
                    error: Some(format!(
                        "endorsement signer {fingerprint} is not in the skill's trusted_endorsers allowlist"
                    )),
                    ..base_report
                });
            }
            let signature_bytes = match base64::engine::general_purpose::STANDARD
                .decode(endorsement.ed25519_sig_base64.as_bytes())
            {
                Ok(bytes) => bytes,
                Err(error) => {
                    return Ok(EndorsementReport {
                        error: Some(format!(
                            "endorsement signature for {fingerprint} is not valid base64: {error}"
                        )),
                        ..base_report
                    })
                }
            };
            let signature = match Signature::from_slice(&signature_bytes) {
                Ok(signature) => signature,
                Err(error) => {
                    return Ok(EndorsementReport {
                        error: Some(format!(
                            "endorsement signature for {fingerprint} is not valid Ed25519 bytes: {error}"
                        )),
                        ..base_report
                    })
                }
            };
            if verifying_key.verify(skill_bytes, &signature).is_err() {
                return Ok(EndorsementReport {
                    error: Some(format!(
                        "endorsement signature for {fingerprint} failed Ed25519 verification"
                    )),
                    ..base_report
                });
            }
            Ok(EndorsementReport {
                trusted: true,
                status: VerificationStatus::Verified,
                ..base_report
            })
        })
        .collect()
}

pub(crate) fn trust_add(from: &str) -> Result<TrustedSignerRecord, String> {
    let verifying_key = verifying_key_from_source(from)?;
    let fingerprint = fingerprint_for_key(&verifying_key);
    let pem = verifying_key
        .to_public_key_pem(LineEnding::LF)
        .map_err(|error| format!("failed to encode public key PEM: {error}"))?;
    let dir = trusted_signers_dir()?;
    fs::create_dir_all(&dir)
        .map_err(|error| format!("failed to create {}: {error}", dir.display()))?;
    let path = dir.join(format!("{fingerprint}.pub"));
    fs::write(&path, pem.as_bytes())
        .map_err(|error| format!("failed to write {}: {error}", path.display()))?;
    Ok(TrustedSignerRecord { fingerprint, path })
}

pub(crate) fn trust_list() -> Result<Vec<TrustedSignerRecord>, String> {
    let dir = trusted_signers_dir()?;
    if !dir.exists() {
        return Ok(Vec::new());
    }
    let mut records = Vec::new();
    let entries =
        fs::read_dir(&dir).map_err(|error| format!("failed to read {}: {error}", dir.display()))?;
    for entry in entries.flatten() {
        let path = entry.path();
        if path.extension().and_then(|ext| ext.to_str()) != Some("pub") {
            continue;
        }
        let raw = fs::read_to_string(&path)
            .map_err(|error| format!("failed to read {}: {error}", path.display()))?;
        let verifying_key = VerifyingKey::from_public_key_pem(&raw)
            .map_err(|error| format!("failed to parse {}: {error}", path.display()))?;
        records.push(TrustedSignerRecord {
            fingerprint: fingerprint_for_key(&verifying_key),
            path,
        });
    }
    records.sort_by(|left, right| left.fingerprint.cmp(&right.fingerprint));
    Ok(records)
}

pub(crate) fn configured_registry_url(anchor: Option<&Path>) -> Option<String> {
    if let Ok(raw) = std::env::var(SIGNER_REGISTRY_URL_ENV) {
        let trimmed = raw.trim();
        if !trimmed.is_empty() {
            return Some(trimmed.to_string());
        }
    }
    load_skills_config(anchor).and_then(|resolved| resolved.config.signer_registry_url)
}

pub(crate) fn load_trust_policy(path: &Path) -> Result<TrustPolicy, String> {
    let raw = fs::read_to_string(path)
        .map_err(|error| format!("failed to read {}: {error}", path.display()))?;
    serde_json::from_str(&raw)
        .map_err(|error| format!("failed to parse trust policy {}: {error}", path.display()))
}

pub(crate) enum TrustedSignerStatus {
    Trusted,
    MissingSigner,
    UntrustedSigner,
}

pub(crate) fn check_trusted_signer(
    fingerprint: &str,
    policy: Option<&TrustPolicy>,
) -> Result<TrustedSignerStatus, String> {
    let registry_url = policy.and_then(|policy| policy.signer_registry_url.as_deref());
    let Some(_) = resolve_verifying_key(fingerprint, registry_url)? else {
        return Ok(TrustedSignerStatus::MissingSigner);
    };
    if policy.is_some_and(|policy| {
        !policy.trusted_signers.is_empty()
            && !policy.trusted_signers.iter().any(|id| id == fingerprint)
    }) {
        return Ok(TrustedSignerStatus::UntrustedSigner);
    }
    Ok(TrustedSignerStatus::Trusted)
}

pub(crate) fn signature_path_for(skill_path: &Path) -> PathBuf {
    append_suffix(skill_path, ".sig")
}

pub(crate) fn load_ed25519_signing_key(private_key_path: &Path) -> Result<SigningKey, String> {
    let private_pem = fs::read_to_string(private_key_path)
        .map_err(|error| format!("failed to read {}: {error}", private_key_path.display()))?;
    SigningKey::from_pkcs8_pem(&private_pem)
        .map_err(|error| format!("failed to parse {}: {error}", private_key_path.display()))
}

fn read_signature_envelope(signature_path: &Path) -> Result<SkillSignatureEnvelope, String> {
    let signature_raw = fs::read_to_string(signature_path)
        .map_err(|error| format!("failed to read {}: {error}", signature_path.display()))?;
    serde_json::from_str(&signature_raw).map_err(|error| {
        format!(
            "{} is not valid {} JSON: {error}",
            signature_path.display(),
            SIG_SCHEMA
        )
    })
}

pub(crate) fn trusted_signers_dir() -> Result<PathBuf, String> {
    user_home_dir()
        .map(|home| home.join(".harn").join("trusted-signers"))
        .ok_or_else(|| "could not determine the current user's home directory".to_string())
}

fn resolve_verifying_key(
    fingerprint: &str,
    registry_url: Option<&str>,
) -> Result<Option<VerifyingKey>, String> {
    let local_path = trusted_signers_dir()?.join(format!("{fingerprint}.pub"));
    if local_path.is_file() {
        let pem = fs::read_to_string(&local_path)
            .map_err(|error| format!("failed to read {}: {error}", local_path.display()))?;
        let key = VerifyingKey::from_public_key_pem(&pem)
            .map_err(|error| format!("failed to parse {}: {error}", local_path.display()))?;
        return Ok(Some(key));
    }

    let Some(registry_url) = registry_url else {
        return Ok(None);
    };
    let pem = match fetch_registry_public_key(registry_url, fingerprint)? {
        Some(pem) => pem,
        None => return Ok(None),
    };
    let key = VerifyingKey::from_public_key_pem(&pem)
        .map_err(|error| format!("failed to parse signer from registry: {error}"))?;
    Ok(Some(key))
}

fn fetch_registry_public_key(
    registry_url: &str,
    fingerprint: &str,
) -> Result<Option<String>, String> {
    let filename = format!("{fingerprint}.pub");
    if let Some(path) = file_url_or_path(registry_url)? {
        let resolved = path.join(filename);
        return match fs::read_to_string(&resolved) {
            Ok(raw) => Ok(Some(raw)),
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
            Err(error) => Err(format!("failed to read {}: {error}", resolved.display())),
        };
    }

    let base = Url::parse(registry_url)
        .map_err(|error| format!("invalid signer registry URL {registry_url:?}: {error}"))?;
    let url = base
        .join(&filename)
        .map_err(|error| format!("failed to resolve signer URL from {registry_url:?}: {error}"))?;
    let response = reqwest::blocking::get(url.clone())
        .map_err(|error| format!("failed to fetch {url}: {error}"))?;
    if response.status() == reqwest::StatusCode::NOT_FOUND {
        return Ok(None);
    }
    let response = response
        .error_for_status()
        .map_err(|error| format!("failed to fetch {url}: {error}"))?;
    response
        .text()
        .map(Some)
        .map_err(|error| format!("failed to read {url}: {error}"))
}

fn verifying_key_from_source(from: &str) -> Result<VerifyingKey, String> {
    let raw = if let Some(path) = file_url_or_path(from)? {
        fs::read_to_string(&path)
            .map_err(|error| format!("failed to read {}: {error}", path.display()))?
    } else {
        let url = Url::parse(from).map_err(|error| format!("invalid URL {from:?}: {error}"))?;
        let response = reqwest::blocking::get(url.clone())
            .map_err(|error| format!("failed to fetch {url}: {error}"))?;
        let response = response
            .error_for_status()
            .map_err(|error| format!("failed to fetch {url}: {error}"))?;
        response
            .text()
            .map_err(|error| format!("failed to read {url}: {error}"))?
    };
    VerifyingKey::from_public_key_pem(&raw)
        .map_err(|error| format!("failed to parse Ed25519 public key: {error}"))
}

fn file_url_or_path(raw: &str) -> Result<Option<PathBuf>, String> {
    if raw.starts_with("http://") || raw.starts_with("https://") {
        return Ok(None);
    }
    if raw.starts_with("file://") {
        let url = Url::parse(raw).map_err(|error| format!("invalid file URL {raw:?}: {error}"))?;
        return url
            .to_file_path()
            .map(Some)
            .map_err(|_| format!("could not convert {raw:?} into a filesystem path"));
    }
    Ok(Some(PathBuf::from(raw)))
}

fn append_suffix(path: &Path, suffix: &str) -> PathBuf {
    let mut raw: OsString = path.as_os_str().to_os_string();
    raw.push(suffix);
    PathBuf::from(raw)
}

fn sha256_hex(bytes: &[u8]) -> String {
    hex::encode(Sha256::digest(bytes))
}

pub(crate) fn fingerprint_for_key(key: &VerifyingKey) -> String {
    hex::encode(Sha256::digest(key.as_bytes()))
}

fn user_home_dir() -> Option<PathBuf> {
    harn_vm::user_dirs::home_dir()
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;

    use crate::env_guard::ScopedEnvVar;
    use crate::tests::common::{cwd_lock::lock_cwd, env_lock::lock_env};

    fn write_skill(path: &Path, body: &str) {
        fs::create_dir_all(path.parent().unwrap()).unwrap();
        fs::write(path, body).unwrap();
    }

    fn set_home(path: &Path) -> ScopedEnvVar {
        ScopedEnvVar::set("HOME", path.to_str().unwrap())
    }

    #[test]
    fn keygen_sign_and_verify_roundtrip() {
        let _cwd = lock_cwd();
        let _env = lock_env().blocking_lock();
        let tmp = tempfile::tempdir().unwrap();
        let _home = set_home(tmp.path());

        let skill = tmp.path().join("skill").join("SKILL.md");
        write_skill(&skill, "---\nname: deploy\n---\nship it\n");
        let keys = generate_keypair(tmp.path().join("signer.pem")).unwrap();
        let signed = sign_skill(&skill, &keys.private_key_path).unwrap();
        let signer = trust_add(keys.public_key_path.to_str().unwrap()).unwrap();
        let report = verify_skill(&skill, &VerifyOptions::default()).unwrap();
        assert_eq!(report.status, VerificationStatus::MissingEndorsement);

        let endorser_keys = generate_keypair(tmp.path().join("endorser.pem")).unwrap();
        trust_add(endorser_keys.public_key_path.to_str().unwrap()).unwrap();
        endorse_skill(&skill, &endorser_keys.private_key_path).unwrap();
        let report = verify_skill(&skill, &VerifyOptions::default()).unwrap();

        assert_eq!(signed.signer_fingerprint, keys.fingerprint);
        assert_eq!(signer.fingerprint, keys.fingerprint);
        assert!(report.is_verified());
        assert_eq!(
            report.signer_fingerprint.as_deref(),
            Some(keys.fingerprint.as_str())
        );
    }

    #[test]
    fn verify_rejects_tampered_skill_payload() {
        let _cwd = lock_cwd();
        let _env = lock_env().blocking_lock();
        let tmp = tempfile::tempdir().unwrap();
        let _home = set_home(tmp.path());

        let skill = tmp.path().join("skill").join("SKILL.md");
        write_skill(&skill, "---\nname: deploy\n---\nship it\n");
        let keys = generate_keypair(tmp.path().join("signer.pem")).unwrap();
        sign_skill(&skill, &keys.private_key_path).unwrap();
        trust_add(keys.public_key_path.to_str().unwrap()).unwrap();
        let endorser_keys = generate_keypair(tmp.path().join("endorser.pem")).unwrap();
        trust_add(endorser_keys.public_key_path.to_str().unwrap()).unwrap();
        endorse_skill(&skill, &endorser_keys.private_key_path).unwrap();
        fs::write(&skill, "---\nname: deploy\n---\nship it now\n").unwrap();

        let report = verify_skill(&skill, &VerifyOptions::default()).unwrap();
        assert_eq!(report.status, VerificationStatus::InvalidSignature);
    }

    #[test]
    fn verify_rejects_wrong_key_signature() {
        let _cwd = lock_cwd();
        let _env = lock_env().blocking_lock();
        let tmp = tempfile::tempdir().unwrap();
        let _home = set_home(tmp.path());

        let skill = tmp.path().join("skill").join("SKILL.md");
        write_skill(&skill, "---\nname: deploy\n---\nship it\n");
        let signing_keys = generate_keypair(tmp.path().join("signer.pem")).unwrap();
        let trusted_keys = generate_keypair(tmp.path().join("trusted.pem")).unwrap();
        sign_skill(&skill, &signing_keys.private_key_path).unwrap();
        let endorser_keys = generate_keypair(tmp.path().join("endorser.pem")).unwrap();
        endorse_skill(&skill, &endorser_keys.private_key_path).unwrap();
        trust_add(trusted_keys.public_key_path.to_str().unwrap()).unwrap();

        let sig_path = signature_path_for(&skill);
        let mut envelope: SkillSignatureEnvelope =
            serde_json::from_str(&fs::read_to_string(&sig_path).unwrap()).unwrap();
        envelope.signer_fingerprint = trusted_keys.fingerprint;
        fs::write(&sig_path, serde_json::to_string_pretty(&envelope).unwrap()).unwrap();

        let report = verify_skill(&skill, &VerifyOptions::default()).unwrap();
        assert_eq!(report.status, VerificationStatus::InvalidSignature);
    }

    #[test]
    fn verify_reports_missing_signer() {
        let _cwd = lock_cwd();
        let _env = lock_env().blocking_lock();
        let tmp = tempfile::tempdir().unwrap();
        let _home = set_home(tmp.path());

        let skill = tmp.path().join("skill").join("SKILL.md");
        write_skill(&skill, "---\nname: deploy\n---\nship it\n");
        let keys = generate_keypair(tmp.path().join("signer.pem")).unwrap();
        sign_skill(&skill, &keys.private_key_path).unwrap();
        let endorser_keys = generate_keypair(tmp.path().join("endorser.pem")).unwrap();
        endorse_skill(&skill, &endorser_keys.private_key_path).unwrap();

        let report = verify_skill(&skill, &VerifyOptions::default()).unwrap();
        assert_eq!(report.status, VerificationStatus::MissingSigner);
        assert!(report.signed);
        assert!(!report.trusted);
    }

    #[test]
    fn verify_honors_allowed_signers() {
        let _cwd = lock_cwd();
        let _env = lock_env().blocking_lock();
        let tmp = tempfile::tempdir().unwrap();
        let _home = set_home(tmp.path());

        let skill = tmp.path().join("skill").join("SKILL.md");
        write_skill(&skill, "---\nname: deploy\n---\nship it\n");
        let keys = generate_keypair(tmp.path().join("signer.pem")).unwrap();
        sign_skill(&skill, &keys.private_key_path).unwrap();
        trust_add(keys.public_key_path.to_str().unwrap()).unwrap();
        let endorser_keys = generate_keypair(tmp.path().join("endorser.pem")).unwrap();
        trust_add(endorser_keys.public_key_path.to_str().unwrap()).unwrap();
        endorse_skill(&skill, &endorser_keys.private_key_path).unwrap();

        let report = verify_skill(
            &skill,
            &VerifyOptions {
                allowed_signers: vec!["not-the-signer".to_string()],
                ..Default::default()
            },
        )
        .unwrap();
        assert_eq!(report.status, VerificationStatus::UntrustedSigner);

        let report = verify_skill(
            &skill,
            &VerifyOptions {
                allowed_signers: vec![keys.fingerprint],
                allowed_endorsers: vec!["not-the-endorser".to_string()],
                ..Default::default()
            },
        )
        .unwrap();
        assert_eq!(report.status, VerificationStatus::UntrustedSigner);
    }

    #[test]
    fn verify_rejects_missing_endorsement() {
        let _cwd = lock_cwd();
        let _env = lock_env().blocking_lock();
        let tmp = tempfile::tempdir().unwrap();
        let _home = set_home(tmp.path());

        let skill = tmp.path().join("skill").join("SKILL.md");
        write_skill(&skill, "---\nname: deploy\n---\nship it\n");
        let keys = generate_keypair(tmp.path().join("signer.pem")).unwrap();
        sign_skill(&skill, &keys.private_key_path).unwrap();
        trust_add(keys.public_key_path.to_str().unwrap()).unwrap();

        let report = verify_skill(&skill, &VerifyOptions::default()).unwrap();
        assert_eq!(report.status, VerificationStatus::MissingEndorsement);
        assert!(report.signed);
        assert!(!report.trusted);
    }
}
