use anchor_lang::prelude::*;

declare_id!("H95dFPhN4AZZbQPHYU34UfCNiiiw7y8ik4ZFSy9sfxZb");

fn participants_are_canonical(agent_a: &Pubkey, agent_b: &Pubkey) -> bool {
    agent_a != agent_b && agent_a.to_bytes() < agent_b.to_bytes()
}

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

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

    pub fn create_bond(ctx: Context<CreateBond>, bond_type: BondType) -> Result<()> {
        let agent_a = ctx.accounts.agent_a.key();
        let agent_b = ctx.accounts.agent_b.key();
        require!(agent_a != agent_b, BondError::DuplicateParticipant);
        // A canonical participant order prevents A-B and B-A duplicate bonds.
        require!(
            participants_are_canonical(&agent_a, &agent_b),
            BondError::NonCanonicalParticipantOrder
        );

        let state = &mut ctx.accounts.bonds_state;
        let bond = &mut ctx.accounts.bond;

        bond.agent_a = agent_a;
        bond.agent_b = agent_b;
        bond.bond_type = bond_type;
        bond.created_at = Clock::get()?.unix_timestamp;
        bond.active = true;
        bond.state = state.key();

        state.total_bonds = state
            .total_bonds
            .checked_add(1)
            .ok_or(BondError::ArithmeticOverflow)?;

        emit!(BondCreated {
            agent_a: bond.agent_a,
            agent_b: bond.agent_b,
            bond_type: bond.bond_type,
            created_at: bond.created_at,
        });
        Ok(())
    }

    /// Dissolution is bilateral. Neither participant can erase the other
    /// participant's relationship record or strand its rent deposit alone.
    pub fn dissolve_bond(ctx: Context<DissolveBond>) -> Result<()> {
        let bond = &mut ctx.accounts.bond;
        require!(bond.active, BondError::BondNotActive);
        bond.active = false;

        let state = &mut ctx.accounts.bonds_state;
        state.total_bonds = state
            .total_bonds
            .checked_sub(1)
            .ok_or(BondError::AccountingInvariantViolation)?;

        emit!(BondDissolved {
            agent_a: bond.agent_a,
            agent_b: bond.agent_b,
            dissolved_at: Clock::get()?.unix_timestamp,
        });
        Ok(())
    }
}

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

#[derive(Accounts)]
pub struct CreateBond<'info> {
    #[account(
        mut,
        seeds = [b"partnership_bonds"],
        bump
    )]
    pub bonds_state: Account<'info, BondsState>,
    #[account(
        init,
        payer = agent_a,
        space = 8 + 32 + 32 + 1 + 8 + 1 + 32,
        seeds = [b"bond", bonds_state.key().as_ref(), agent_a.key().as_ref(), agent_b.key().as_ref()],
        bump
    )]
    pub bond: Account<'info, PartnershipBond>,
    #[account(mut)]
    pub agent_a: Signer<'info>,
    pub agent_b: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct DissolveBond<'info> {
    #[account(
        mut,
        seeds = [b"partnership_bonds"],
        bump
    )]
    pub bonds_state: Account<'info, BondsState>,
    #[account(
        mut,
        seeds = [b"bond", bonds_state.key().as_ref(), bond.agent_a.as_ref(), bond.agent_b.as_ref()],
        bump,
        close = agent_a,
        constraint = bond.state == bonds_state.key() @ BondError::InvalidBondState,
        constraint = bond.agent_a == agent_a.key() @ BondError::Unauthorized,
        constraint = bond.agent_b == agent_b.key() @ BondError::Unauthorized
    )]
    pub bond: Account<'info, PartnershipBond>,
    #[account(mut)]
    pub agent_a: Signer<'info>,
    pub agent_b: Signer<'info>,
}

#[account]
pub struct BondsState {
    pub authority: Pubkey,
    pub total_bonds: u64,
    pub created_at: i64,
}

#[account]
pub struct PartnershipBond {
    pub agent_a: Pubkey,
    pub agent_b: Pubkey,
    pub bond_type: BondType,
    pub created_at: i64,
    pub active: bool,
    pub state: Pubkey,
}

#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, PartialEq, Eq)]
pub enum BondType {
    Collaboration,
    Oversight,
    Delegation,
    Peer,
}

#[event]
pub struct BondCreated {
    pub agent_a: Pubkey,
    pub agent_b: Pubkey,
    pub bond_type: BondType,
    pub created_at: i64,
}

#[event]
pub struct BondDissolved {
    pub agent_a: Pubkey,
    pub agent_b: Pubkey,
    pub dissolved_at: i64,
}

#[error_code]
pub enum BondError {
    #[msg("Bond is not active")]
    BondNotActive,
    #[msg("Unauthorized: only bond participants can dissolve")]
    Unauthorized,
    #[msg("A participant cannot bond with itself")]
    DuplicateParticipant,
    #[msg("Participants must use canonical public-key order")]
    NonCanonicalParticipantOrder,
    #[msg("Bond is not linked to the supplied bonds state")]
    InvalidBondState,
    #[msg("Arithmetic overflow")]
    ArithmeticOverflow,
    #[msg("Bond accounting invariant violated")]
    AccountingInvariantViolation,
    #[msg("Bootstrap is disabled until a source-backed partner authority design is approved")]
    BootstrapDisabled,
}

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

    #[test]
    fn participant_order_is_unique_and_self_bonds_are_rejected() {
        let first = Pubkey::new_from_array([1; 32]);
        let second = Pubkey::new_from_array([2; 32]);
        assert!(participants_are_canonical(&first, &second));
        assert!(!participants_are_canonical(&second, &first));
        assert!(!participants_are_canonical(&first, &first));
    }
}
