use anchor_lang::prelude::*;

declare_id!("91Q3sPMksUR1SUciotWzpKtZLVF8MLQ8gyNPvsDvVXnL");

// Solana limits each individual PDA seed to 32 bytes. Keep the accepted
// user-controlled type within that limit because it is used directly as a seed.
const MAX_VALIDATION_TYPE_SEED_BYTES: usize = 32;

#[program]
pub mod validation_registry {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let deployment_authorized = false;
        require!(deployment_authorized, ValidationError::BootstrapDisabled);
        let state = &mut ctx.accounts.validation_state;
        state.authority = ctx.accounts.authority.key();
        state.total_validations = 0;
        state.created_at = Clock::get()?.unix_timestamp;
        Ok(())
    }

    pub fn submit_validation(
        ctx: Context<SubmitValidation>,
        validation_type: String,
    ) -> Result<()> {
        require!(
            validation_type.len() <= MAX_VALIDATION_TYPE_SEED_BYTES,
            ValidationError::TypeTooLong
        );

        let state = &mut ctx.accounts.validation_state;
        let validation = &mut ctx.accounts.validation;

        validation.agent = ctx.accounts.agent.key();
        validation.status = ValidationStatus::Pending;
        validation.validation_type = validation_type.clone();
        validation.submitted_at = Clock::get()?.unix_timestamp;
        validation.validated_at = 0;

        state.total_validations = state
            .total_validations
            .checked_add(1)
            .ok_or(ValidationError::ArithmeticOverflow)?;

        emit!(ValidationSubmitted {
            agent: validation.agent,
            validation_type,
            submitted_at: validation.submitted_at,
        });
        Ok(())
    }

    pub fn approve_validation(ctx: Context<ApproveValidation>) -> Result<()> {
        let validation = &mut ctx.accounts.validation;
        require!(
            validation.status == ValidationStatus::Pending,
            ValidationError::AlreadyResolved
        );

        validation.status = ValidationStatus::Approved;
        validation.validated_at = Clock::get()?.unix_timestamp;

        emit!(ValidationApproved {
            agent: validation.agent,
            validation_type: validation.validation_type.clone(),
            validated_at: validation.validated_at,
        });
        Ok(())
    }

    pub fn reject_validation(ctx: Context<ApproveValidation>) -> Result<()> {
        let validation = &mut ctx.accounts.validation;
        require!(
            validation.status == ValidationStatus::Pending,
            ValidationError::AlreadyResolved
        );

        validation.status = ValidationStatus::Rejected;
        validation.validated_at = Clock::get()?.unix_timestamp;

        emit!(ValidationRejected {
            agent: validation.agent,
            validation_type: validation.validation_type.clone(),
            rejected_at: validation.validated_at,
        });
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        payer = authority,
        space = 8 + 32 + 8 + 8,
        seeds = [b"validation"],
        bump
    )]
    pub validation_state: Account<'info, ValidationState>,
    #[account(mut)]
    pub authority: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
#[instruction(validation_type: String)]
pub struct SubmitValidation<'info> {
    #[account(
        mut,
        seeds = [b"validation"],
        bump
    )]
    pub validation_state: Account<'info, ValidationState>,
    #[account(
        init,
        payer = agent,
        space = 8 + 32 + 1 + 4 + 64 + 8 + 8,
        seeds = [b"val", agent.key().as_ref(), validation_type.as_bytes()],
        bump
    )]
    pub validation: Account<'info, AgentValidation>,
    #[account(mut)]
    pub agent: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct ApproveValidation<'info> {
    #[account(
        seeds = [b"validation"],
        bump,
        has_one = authority
    )]
    pub validation_state: Account<'info, ValidationState>,
    #[account(mut)]
    pub validation: Account<'info, AgentValidation>,
    pub authority: Signer<'info>,
}

#[account]
pub struct ValidationState {
    pub authority: Pubkey,
    pub total_validations: u64,
    pub created_at: i64,
}

#[account]
pub struct AgentValidation {
    pub agent: Pubkey,
    pub status: ValidationStatus,
    pub validation_type: String,
    pub submitted_at: i64,
    pub validated_at: i64,
}

#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, PartialEq, Eq)]
pub enum ValidationStatus {
    Pending,
    Approved,
    Rejected,
}

#[event]
pub struct ValidationSubmitted {
    pub agent: Pubkey,
    pub validation_type: String,
    pub submitted_at: i64,
}

#[event]
pub struct ValidationApproved {
    pub agent: Pubkey,
    pub validation_type: String,
    pub validated_at: i64,
}

#[event]
pub struct ValidationRejected {
    pub agent: Pubkey,
    pub validation_type: String,
    pub rejected_at: i64,
}

#[error_code]
pub enum ValidationError {
    #[msg("Validation type exceeds the 32-byte PDA seed limit")]
    TypeTooLong,
    #[msg("Validation has already reached a terminal decision")]
    AlreadyResolved,
    #[msg("Arithmetic overflow")]
    ArithmeticOverflow,
    #[msg("Bootstrap is disabled until a source-backed partner authority design is approved")]
    BootstrapDisabled,
}

#[cfg(test)]
mod tests {
    use super::ValidationStatus;

    #[test]
    fn terminal_decisions_are_distinct_from_pending() {
        assert!(ValidationStatus::Pending != ValidationStatus::Approved);
        assert!(ValidationStatus::Pending != ValidationStatus::Rejected);
        assert!(ValidationStatus::Approved != ValidationStatus::Rejected);
    }
}
