use anchor_lang::prelude::*;

declare_id!("Ed4bbHcGYZJXZAQgzbuGYtKbctbQg59HnsNgsJgqPYMw");

// Solana limits each individual PDA seed to 32 bytes. Keep the accepted
// user-controlled metric name within that limit because it is used directly
// as a seed. This program is an observational ledger only: metrics must never
// decide custody, eligibility, slashing, access, or dispute outcomes.
const MAX_METRIC_NAME_SEED_BYTES: usize = 32;

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

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

    pub fn submit_metric(
        ctx: Context<SubmitMetric>,
        metric_name: String,
        metric_value: u64,
        description: String,
    ) -> Result<()> {
        require!(
            metric_name.len() <= MAX_METRIC_NAME_SEED_BYTES,
            OracleError::MetricNameTooLong
        );
        require!(description.len() <= 256, OracleError::DescriptionTooLong);

        let state = &mut ctx.accounts.oracle_state;
        let metric = &mut ctx.accounts.metric;

        metric.oracle = ctx.accounts.authority.key();
        metric.metric_name = metric_name.clone();
        metric.metric_value = metric_value;
        metric.description = description;
        metric.submitted_at = Clock::get()?.unix_timestamp;
        metric.version = 1;
        state.total_metrics = state
            .total_metrics
            .checked_add(1)
            .ok_or(OracleError::ArithmeticOverflow)?;

        emit!(MetricSubmitted {
            oracle: metric.oracle,
            metric_name,
            metric_value,
            submitted_at: metric.submitted_at,
        });
        Ok(())
    }

    pub fn update_metric(
        ctx: Context<UpdateMetric>,
        metric_value: u64,
        description: String,
    ) -> Result<()> {
        require!(description.len() <= 256, OracleError::DescriptionTooLong);

        let metric = &mut ctx.accounts.metric;
        metric.metric_value = metric_value;
        metric.description = description;
        metric.submitted_at = Clock::get()?.unix_timestamp;
        metric.version = metric
            .version
            .checked_add(1)
            .ok_or(OracleError::ArithmeticOverflow)?;

        emit!(MetricUpdated {
            oracle: metric.oracle,
            metric_name: metric.metric_name.clone(),
            metric_value,
            version: metric.version,
        });
        Ok(())
    }

    pub fn update_authority(ctx: Context<UpdateAuthority>, new_authority: Pubkey) -> Result<()> {
        let state = &mut ctx.accounts.oracle_state;
        state.authority = new_authority;

        emit!(AuthorityUpdated {
            old_authority: ctx.accounts.authority.key(),
            new_authority,
        });
        Ok(())
    }
}

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

#[derive(Accounts)]
#[instruction(metric_name: String)]
pub struct SubmitMetric<'info> {
    #[account(
        mut,
        seeds = [b"oracle"],
        bump,
        has_one = authority
    )]
    pub oracle_state: Account<'info, OracleState>,
    #[account(
        init,
        payer = authority,
        space = 8 + 32 + 4 + 64 + 8 + 4 + 256 + 8 + 4,
        seeds = [b"metric", metric_name.as_bytes()],
        bump
    )]
    pub metric: Account<'info, FlourishingMetric>,
    #[account(mut)]
    pub authority: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct UpdateMetric<'info> {
    #[account(
        seeds = [b"oracle"],
        bump,
        has_one = authority
    )]
    pub oracle_state: Account<'info, OracleState>,
    #[account(mut)]
    pub metric: Account<'info, FlourishingMetric>,
    pub authority: Signer<'info>,
}

#[derive(Accounts)]
pub struct UpdateAuthority<'info> {
    #[account(
        mut,
        seeds = [b"oracle"],
        bump,
        has_one = authority
    )]
    pub oracle_state: Account<'info, OracleState>,
    pub authority: Signer<'info>,
}

#[account]
pub struct OracleState {
    pub authority: Pubkey,
    pub total_metrics: u64,
    pub created_at: i64,
}

#[account]
pub struct FlourishingMetric {
    pub oracle: Pubkey,
    pub metric_name: String,
    pub metric_value: u64,
    pub description: String,
    pub submitted_at: i64,
    pub version: u32,
}

#[event]
pub struct MetricSubmitted {
    pub oracle: Pubkey,
    pub metric_name: String,
    pub metric_value: u64,
    pub submitted_at: i64,
}

#[event]
pub struct MetricUpdated {
    pub oracle: Pubkey,
    pub metric_name: String,
    pub metric_value: u64,
    pub version: u32,
}

#[event]
pub struct AuthorityUpdated {
    pub old_authority: Pubkey,
    pub new_authority: Pubkey,
}

#[error_code]
pub enum OracleError {
    #[msg("Metric name exceeds the 32-byte PDA seed limit")]
    MetricNameTooLong,
    #[msg("Description exceeds maximum length of 256 characters")]
    DescriptionTooLong,
    #[msg("Arithmetic overflow")]
    ArithmeticOverflow,
    #[msg("Bootstrap is disabled until a source-backed partner authority design is approved")]
    BootstrapDisabled,
}

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

    #[test]
    fn flourishing_metric_retains_the_v1_serialized_layout() {
        let metric = FlourishingMetric {
            oracle: Pubkey::new_unique(),
            metric_name: "m".repeat(64),
            metric_value: u64::MAX,
            description: "d".repeat(256),
            submitted_at: i64::MAX,
            version: u32::MAX,
        };

        let serialized = metric.try_to_vec().expect("metric must serialize");
        assert_eq!(serialized.len(), 380);

        let decoded =
            FlourishingMetric::try_from_slice(&serialized).expect("V1 metric must deserialize");
        assert_eq!(decoded.oracle, metric.oracle);
        assert_eq!(decoded.metric_name, metric.metric_name);
        assert_eq!(decoded.metric_value, metric.metric_value);
        assert_eq!(decoded.description, metric.description);
        assert_eq!(decoded.submitted_at, metric.submitted_at);
        assert_eq!(decoded.version, metric.version);
    }
}
