use runx_contracts::sha256_hex;

use crate::policy::{
    AuthorityProof, AuthorityProofApprovalDecision, AuthorityProofApprovalDecisionValue,
    AuthorityProofCredentialMaterial, AuthorityProofCredentialMaterialStatus,
    AuthorityProofMetadata, AuthorityProofRedaction, AuthorityProofRedactionSecretMaterial,
    AuthorityProofRedactionStatus, AuthorityProofRedactionStream, AuthorityProofRequested,
    AuthorityProofSandbox, AuthorityProofSchemaVersion, BuildAuthorityProofOptions,
    CredentialEnvelope, LocalScopeAdmissionOptions, ScopeAdmission, ScopeAdmissionStatus,
    credential_grant::{CredentialGrantRequirement, credential_grant_requirement},
    scope::unique_strings,
};

use super::{
    admission::build_local_scope_admission,
    sandbox_summary::summarize_authority_sandbox,
    util::{non_empty_option, non_empty_vec},
};

#[must_use]
pub fn build_authority_proof(options: &BuildAuthorityProofOptions) -> AuthorityProof {
    let requirement = credential_grant_requirement(options.auth.as_ref());
    let scope_admission = options.scope_admission.clone().unwrap_or_else(|| {
        build_local_scope_admission(
            options.auth.as_ref(),
            &options.grants,
            &LocalScopeAdmissionOptions {
                connected_auth_checked_at: options.connected_auth_checked_at.clone(),
                ..LocalScopeAdmissionOptions::default()
            },
        )
    });
    let sandbox = summarize_authority_sandbox(
        options.sandbox_metadata.as_ref(),
        options.sandbox_declaration.as_ref(),
        options.approval.as_ref(),
    );

    AuthorityProof {
        schema_version: AuthorityProofSchemaVersion::V1,
        run_id: non_empty_option(options.run_id.clone()),
        skill_name: options.skill_name.clone().into(),
        source_type: options.source_type.clone().into(),
        requested: authority_proof_requested(&requirement, &sandbox, options),
        scope_admission: scope_admission.clone(),
        credential_material: credential_material_proof(
            options.credential.as_ref(),
            requirement.as_ref(),
            &scope_admission,
        ),
        sandbox,
        approval_gate: options.approval.as_ref().map(approval_decision),
        redaction: authority_redaction(),
    }
}

#[must_use]
pub fn build_authority_proof_metadata(
    options: &BuildAuthorityProofOptions,
) -> AuthorityProofMetadata {
    AuthorityProofMetadata {
        authority_proof: build_authority_proof(options),
    }
}

fn authority_proof_requested(
    requirement: &Option<CredentialGrantRequirement>,
    sandbox: &Option<AuthorityProofSandbox>,
    options: &BuildAuthorityProofOptions,
) -> AuthorityProofRequested {
    AuthorityProofRequested {
        connected_auth: requirement.is_some(),
        scopes: requirement.as_ref().map_or_else(Vec::new, |value| {
            non_empty_vec(unique_strings(&value.scopes))
        }),
        mutating: options.mutating.unwrap_or(false),
        scope_family: requirement
            .as_ref()
            .and_then(|value| non_empty_option(value.scope_family.clone())),
        authority_kind: requirement
            .as_ref()
            .and_then(|value| value.authority_kind.clone()),
        target_repo: requirement
            .as_ref()
            .and_then(|value| non_empty_option(value.target_repo.clone())),
        target_locator: requirement
            .as_ref()
            .and_then(|value| non_empty_option(value.target_locator.clone())),
        sandbox_profile: sandbox.as_ref().map(|value| value.profile.clone()),
    }
}

fn credential_material_proof(
    credential: Option<&CredentialEnvelope>,
    requirement: Option<&CredentialGrantRequirement>,
    scope_admission: &ScopeAdmission,
) -> AuthorityProofCredentialMaterial {
    if let Some(credential) = credential {
        return resolved_credential_material(credential);
    }
    match requirement {
        None => AuthorityProofCredentialMaterial {
            status: AuthorityProofCredentialMaterialStatus::NotRequested,
            ..empty_credential_material()
        },
        Some(requirement) => unresolved_credential_material(requirement, scope_admission),
    }
}

fn resolved_credential_material(
    credential: &CredentialEnvelope,
) -> AuthorityProofCredentialMaterial {
    AuthorityProofCredentialMaterial {
        status: AuthorityProofCredentialMaterialStatus::Resolved,
        grant_id: Some(credential.grant_id.clone()),
        provider: Some(credential.provider.clone()),
        provider_reference: Some(credential.provider_reference.clone()),
        scopes: Some(credential.scopes.clone()),
        grant_reference: credential.grant_reference.clone(),
        material_ref_hash: Some(sha256_hex(credential.material_ref.as_bytes()).into()),
        ..empty_credential_material()
    }
}

fn unresolved_credential_material(
    requirement: &CredentialGrantRequirement,
    scope_admission: &ScopeAdmission,
) -> AuthorityProofCredentialMaterial {
    AuthorityProofCredentialMaterial {
        status: if scope_admission.status == ScopeAdmissionStatus::Deny {
            AuthorityProofCredentialMaterialStatus::Denied
        } else {
            AuthorityProofCredentialMaterialStatus::NotResolved
        },
        grant_id: scope_admission.grant_id.clone(),
        provider: Some(requirement.provider.clone().into()),
        scopes: Some(non_empty_vec(unique_strings(&requirement.scopes))),
        scope_family: non_empty_option(requirement.scope_family.clone()),
        authority_kind: requirement.authority_kind.clone(),
        target_repo: non_empty_option(requirement.target_repo.clone()),
        target_locator: non_empty_option(requirement.target_locator.clone()),
        ..empty_credential_material()
    }
}

fn approval_decision(
    approval: &crate::policy::AuthorityProofApproval,
) -> AuthorityProofApprovalDecision {
    AuthorityProofApprovalDecision {
        gate_id: approval.gate.id.clone().into(),
        gate_type: approval
            .gate
            .gate_type
            .clone()
            .unwrap_or_else(|| "unspecified".to_owned())
            .into(),
        decision: if approval.approved {
            AuthorityProofApprovalDecisionValue::Approved
        } else {
            AuthorityProofApprovalDecisionValue::Denied
        },
        reason: non_empty_option(approval.gate.reason.clone()),
    }
}

fn authority_redaction() -> AuthorityProofRedaction {
    AuthorityProofRedaction {
        status: AuthorityProofRedactionStatus::Applied,
        secret_material: AuthorityProofRedactionSecretMaterial::Omitted,
        stdout: AuthorityProofRedactionStream::Hashed,
        stderr: AuthorityProofRedactionStream::Hashed,
        metadata_secret_keys: vec![
            "token-like metadata keys".into(),
            "api-key-like metadata keys".into(),
            "password-like metadata keys".into(),
            "client-secret-like metadata keys".into(),
            "raw-secret-like metadata keys".into(),
        ],
    }
}

fn empty_credential_material() -> AuthorityProofCredentialMaterial {
    AuthorityProofCredentialMaterial {
        status: AuthorityProofCredentialMaterialStatus::NotRequested,
        grant_id: None,
        provider: None,
        provider_reference: None,
        scopes: None,
        scope_family: None,
        authority_kind: None,
        target_repo: None,
        target_locator: None,
        grant_reference: None,
        material_ref_hash: None,
    }
}
