use crate::envelope::PublishEnvelope;
use crate::error::{TrazaeoError, TrazaeoResult};
use crate::proof_log::{
    build_proof_log_commitment, verify_proof_log_commitment_linkage, ProofLogCommitment,
    ProofLogPublishResult, ProofLogReceipt,
};
use crate::solana::{
    cluster_name, commit_anchor_v1, get_anchor_account_by_pda, get_transaction, program_id,
    CommitAnchorV1Payload, SolanaClient, TxResult,
};

#[derive(Debug, Clone, Copy)]
pub struct PublishProofLogCommitRequest<'a> {
    pub envelope: &'a PublishEnvelope,
    pub client: &'a SolanaClient,
    pub attestor_pubkey: [u8; 32],
    pub committed_at: &'a str,
    pub committed_unix_seconds: i64,
    pub prev_entry_hash: [u8; 32],
    pub attestor_key_ref: &'a str,
}

fn build_proof_log_receipt(tx: &TxResult) -> ProofLogReceipt {
    ProofLogReceipt {
        entry_id: tx.signature.clone(),
        network: tx.cluster.clone(),
        verifier_ref: tx.program_id.clone(),
        inclusion_height: tx.slot,
        finalized: tx.finalized,
        locator: tx.anchor_account_pda.clone(),
    }
}

/// Verifies proof-log linkage against the on-chain Solana adaptor state.
pub fn verify_proof_log_commitment_onchain(
    envelope: &PublishEnvelope,
    commitment: &ProofLogCommitment,
    client: &SolanaClient,
    expected_cluster: &str,
    expected_program_id: &str,
) -> TrazaeoResult<()> {
    verify_proof_log_commitment_linkage(envelope, commitment).map_err(|_| {
        TrazaeoError::invalid_input(
            "verify proof log linkage onchain",
            "proof-log linkage mismatch against envelope",
        )
    })?;

    let tx = get_transaction(client, &commitment.entry_id)?.ok_or_else(|| {
        TrazaeoError::external(
            "verify proof log linkage onchain",
            "proof-log entry not found on chain",
        )
    })?;
    if !tx.finalized {
        return Err(TrazaeoError::external(
            "verify proof log linkage onchain",
            "proof-log entry not finalized",
        ));
    }
    if tx.cluster != expected_cluster {
        return Err(TrazaeoError::external(
            "verify proof log linkage onchain",
            "proof-log entry cluster mismatch",
        ));
    }
    if tx.program_id != expected_program_id {
        return Err(TrazaeoError::external(
            "verify proof log linkage onchain",
            "proof-log entry program mismatch",
        ));
    }

    let account = get_anchor_account_by_pda(client, &tx.anchor_account_pda)?.ok_or_else(|| {
        TrazaeoError::external(
            "verify proof log linkage onchain",
            "proof-log account not found",
        )
    })?;
    if hex::encode(account.anchored_envelope_hash) != commitment.envelope_hash {
        return Err(TrazaeoError::external(
            "verify proof log linkage onchain",
            "proof-log account envelope hash mismatch",
        ));
    }
    if hex::encode(account.anchored_checkpoint_hash) != commitment.checkpoint_hash {
        return Err(TrazaeoError::external(
            "verify proof log linkage onchain",
            "proof-log account checkpoint hash mismatch",
        ));
    }
    if hex::encode(account.anchored_root_hash) != commitment.log_root_hash {
        return Err(TrazaeoError::external(
            "verify proof log linkage onchain",
            "proof-log account log root hash mismatch",
        ));
    }

    Ok(())
}

/// Commits a publish envelope through the Solana proof-log adaptor.
pub fn commit_publish_proof_log(
    request: PublishProofLogCommitRequest<'_>,
) -> TrazaeoResult<ProofLogPublishResult> {
    let anchored_envelope_hash = blake3::hash(&request.envelope.canonical_signed_bytes());
    let anchored_checkpoint_hash = hex::decode(&request.envelope.checkpoint_manifest_hash)
        .map_err(|e| {
            TrazaeoError::invalid_input(
                "commit publish proof log",
                format!("invalid checkpoint_manifest_hash hex: {e}"),
            )
        })?;
    let anchored_checkpoint_hash: [u8; 32] = anchored_checkpoint_hash
        .as_slice()
        .try_into()
        .map_err(|_| {
            TrazaeoError::invalid_input(
                "commit publish proof log",
                "checkpoint_manifest_hash must be exactly 32 bytes",
            )
        })?;
    let anchored_log_root_hash =
        hex::decode(&request.envelope.checkpoint_log_root_hash).map_err(|e| {
            TrazaeoError::invalid_input(
                "commit publish proof log",
                format!("invalid checkpoint_log_root_hash hex: {e}"),
            )
        })?;
    let anchored_log_root_hash: [u8; 32] =
        anchored_log_root_hash.as_slice().try_into().map_err(|_| {
            TrazaeoError::invalid_input(
                "commit publish proof log",
                "checkpoint_log_root_hash must be exactly 32 bytes",
            )
        })?;

    let payload = CommitAnchorV1Payload {
        schema_version: 1,
        anchored_envelope_hash: *anchored_envelope_hash.as_bytes(),
        anchored_checkpoint_hash,
        anchored_root_hash: anchored_log_root_hash,
        anchored_unix_seconds: request.committed_unix_seconds,
        prev_anchor_hash: request.prev_entry_hash,
    };
    let tx = commit_anchor_v1(request.client, request.attestor_pubkey, payload)?;
    let commitment = build_proof_log_commitment(
        request.envelope,
        request.committed_at,
        &tx.signature,
        request.attestor_key_ref,
    );

    verify_proof_log_commitment_onchain(
        request.envelope,
        &commitment,
        request.client,
        &cluster_name(request.client),
        &program_id(request.client),
    )?;

    Ok(ProofLogPublishResult {
        commitment,
        receipt: build_proof_log_receipt(&tx),
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::envelope::{Attestation, PublishEnvelope};
    use crate::solana::{commit_anchor_v1, CommitAnchorV1Payload, SolanaConfig};

    /// Handles publish.
    fn publish() -> PublishEnvelope {
        PublishEnvelope {
            schema_version: "1.0.0".to_string(),
            envelope_type: "publish".to_string(),
            issued_at: "2026-01-01T00:00:00Z".to_string(),
            subject_id: "publish-1".to_string(),
            dataset_id: "sst".to_string(),
            dataset_version: "v1".to_string(),
            input_refs: vec!["obj://in".to_string()],
            output_refs: vec!["obj://out".to_string()],
            published_artifacts: vec![crate::checkpoint::CheckpointArtifact {
                artifact_id: "artifact-1".to_string(),
                content_root_hash:
                    "0000000000000000000000000000000000000000000000000000000000000000".to_string(),
                content_descriptor_ref: None,
                content_descriptor_hash: None,
                media_type: "application/vnd+zarr".to_string(),
            }],
            primary_artifact_id: "artifact-1".to_string(),
            checkpoint_manifest_ref: "checkpoint://1".to_string(),
            checkpoint_manifest_hash:
                "0000000000000000000000000000000000000000000000000000000000000000".to_string(),
            checkpoint_id: "checkpoint-1".to_string(),
            checkpoint_log_root_hash:
                "0000000000000000000000000000000000000000000000000000000000000000".to_string(),
            lineage_refs: vec!["capture://1".to_string()],
            verification_policy_id: "verify-default".to_string(),
            attestations: vec![Attestation {
                signer_id: "s".to_string(),
                key_id: "k".to_string(),
                signature: "sig".to_string(),
                signed_at: "2026-01-01T00:00:00Z".to_string(),
            }],
            key_id: "key-1".to_string(),
            stac_refs: vec![],
            reward_context_ref: None,
            reward_context_hash: None,
            provenance_start_mode: "transport_capture".to_string(),
            bootstrap_origin_label: None,
            reward_eligible: false,
        }
    }

    /// Tests that proof-log commitment links to publish envelope.
    #[test]
    fn proof_log_commitment_links_to_publish_envelope() {
        let env = publish();
        let commitment =
            build_proof_log_commitment(&env, "2026-01-01T00:01:00Z", "tx-1", "attestor-key");
        assert!(verify_proof_log_commitment_linkage(&env, &commitment).is_ok());
    }

    /// Tests that proof-log linkage fails when root mismatch.
    #[test]
    fn proof_log_linkage_fails_when_root_mismatch() {
        let env = publish();
        let mut commitment =
            build_proof_log_commitment(&env, "2026-01-01T00:01:00Z", "tx-1", "attestor-key");
        commitment.log_root_hash = "bad".to_string();
        assert!(verify_proof_log_commitment_linkage(&env, &commitment).is_err());
    }

    /// Tests that onchain proof-log verification happy path.
    #[test]
    fn onchain_proof_log_verification_happy_path() {
        let env = publish();
        let client = crate::solana::init_solana_client(&SolanaConfig {
            cluster: "solana-testnet".to_string(),
            program_id: "program-1".to_string(),
        });

        let payload = CommitAnchorV1Payload {
            schema_version: 1,
            anchored_envelope_hash: hex::decode(hex::encode(
                blake3::hash(&env.canonical_signed_bytes()).as_bytes(),
            ))
            .expect("hex decode")
            .try_into()
            .expect("fixed array"),
            anchored_checkpoint_hash: hex::decode(&env.checkpoint_manifest_hash)
                .expect("hex decode")
                .try_into()
                .expect("fixed array"),
            anchored_root_hash: hex::decode(&env.checkpoint_log_root_hash)
                .expect("hex decode")
                .try_into()
                .expect("fixed array"),
            anchored_unix_seconds: 1700000000,
            prev_anchor_hash: [0u8; 32],
        };
        let tx = commit_anchor_v1(&client, [7u8; 32], payload).expect("commit");

        let commitment = ProofLogCommitment {
            entry_id: tx.signature,
            envelope_hash: hex::encode(blake3::hash(&env.canonical_signed_bytes()).as_bytes()),
            checkpoint_hash: env.checkpoint_manifest_hash.clone(),
            log_root_hash: env.checkpoint_log_root_hash.clone(),
            committed_at: "2026-01-01T00:00:00Z".to_string(),
            attestor_key_ref: "attestor-key".to_string(),
        };

        assert!(verify_proof_log_commitment_onchain(
            &env,
            &commitment,
            &client,
            "solana-testnet",
            "program-1"
        )
        .is_ok());
    }

    /// Tests that onchain proof-log verification rejects wrong cluster.
    #[test]
    fn onchain_proof_log_verification_rejects_wrong_cluster() {
        let env = publish();
        let client = crate::solana::init_solana_client(&SolanaConfig {
            cluster: "solana-testnet".to_string(),
            program_id: "program-1".to_string(),
        });

        let payload = CommitAnchorV1Payload {
            schema_version: 1,
            anchored_envelope_hash: hex::decode(hex::encode(
                blake3::hash(&env.canonical_signed_bytes()).as_bytes(),
            ))
            .expect("hex decode")
            .try_into()
            .expect("fixed array"),
            anchored_checkpoint_hash: hex::decode(&env.checkpoint_manifest_hash)
                .expect("hex decode")
                .try_into()
                .expect("fixed array"),
            anchored_root_hash: hex::decode(&env.checkpoint_log_root_hash)
                .expect("hex decode")
                .try_into()
                .expect("fixed array"),
            anchored_unix_seconds: 1700000000,
            prev_anchor_hash: [0u8; 32],
        };
        let tx = commit_anchor_v1(&client, [7u8; 32], payload).expect("commit");

        let commitment = ProofLogCommitment {
            entry_id: tx.signature,
            envelope_hash: hex::encode(blake3::hash(&env.canonical_signed_bytes()).as_bytes()),
            checkpoint_hash: env.checkpoint_manifest_hash.clone(),
            log_root_hash: env.checkpoint_log_root_hash.clone(),
            committed_at: "2026-01-01T00:00:00Z".to_string(),
            attestor_key_ref: "attestor-key".to_string(),
        };

        assert!(verify_proof_log_commitment_onchain(
            &env,
            &commitment,
            &client,
            "solana-mainnet",
            "program-1"
        )
        .is_err());
    }

    /// Tests that commit publish proof-log entry returns verified result.
    #[test]
    fn commit_publish_proof_log_returns_verified_result() {
        let env = publish();
        let client = crate::solana::init_solana_client(&SolanaConfig {
            cluster: "solana-testnet".to_string(),
            program_id: "program-1".to_string(),
        });

        let result = commit_publish_proof_log(PublishProofLogCommitRequest {
            envelope: &env,
            client: &client,
            attestor_pubkey: [7u8; 32],
            committed_at: "2026-01-01T00:00:00Z",
            committed_unix_seconds: 1700000000,
            prev_entry_hash: [0u8; 32],
            attestor_key_ref: "attestor-key",
        })
        .expect("commit publish proof log");

        assert!(verify_proof_log_commitment_linkage(&env, &result.commitment).is_ok());
        assert!(verify_proof_log_commitment_onchain(
            &env,
            &result.commitment,
            &client,
            "solana-testnet",
            "program-1"
        )
        .is_ok());
    }
}
