use serde::{Deserialize, Serialize};
use steel::*;

use crate::state::{driller_pda, Treasury};

use super::OilAccount;


#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, Serialize, Deserialize)]
pub struct Driller {
    /// The authority of this driller account.
    pub authority: Pubkey,

    /// The driller's prospects in the current round.
    pub deployed: [u64; 25],

    /// The cumulative amount of SOL deployed on each square prior to this driller's move.
    pub cumulative: [u64; 25],

    /// SOL witheld in reserve to pay for checkpointing.
    pub checkpoint_fee: u64,

    /// The last round that this driller checkpointed.
    pub checkpoint_id: u64,

    /// The last time this driller claimed OIL rewards.
    pub last_claim_oil_at: i64,

    /// The last time this driller claimed SOL rewards.
    pub last_claim_sol_at: i64,

    /// The rewards factor last time rewards were updated on this driller account.
    pub rewards_factor: Numeric,

    /// The amount of SOL this driller can claim.
    pub rewards_sol: u64,

    /// The amount of OIL this driller can claim.
    pub rewards_oil: u64,

    /// The amount of OIL this driller has earned from claim fees.
    pub refined_oil: u64,

    /// The ID of the round this driller last played in.
    pub round_id: u64,

    /// The total amount of SOL this driller has mined across all blocks.
    pub lifetime_rewards_sol: u64,

    /// The total amount of OIL this driller has mined across all blocks.
    pub lifetime_rewards_oil: u64,

    /// The total amount of OIL this driller has deployed across all rounds.
    pub lifetime_deployed: u64,

    /// The pubkey of the referrer who referred this driller.
    /// If this is Pubkey::default(), the driller has no referrer (either old account or no referral used).
    pub referrer: Pubkey,

    /// The pooled deployed amount of this driller.
    pub pooled_deployed: u64,
}

impl Driller {
    pub fn pda(&self) -> (Pubkey, u8) {
        driller_pda(self.authority)
    }

    pub fn claim_oil(&mut self, clock: &Clock, treasury: &mut Treasury) -> u64 {
        self.update_rewards(treasury);
        let refined_oil = self.refined_oil;
        let rewards_oil = self.rewards_oil;
        let mut amount = refined_oil + rewards_oil;
        self.refined_oil = 0;
        self.rewards_oil = 0;

        // Charge a 10% fee and share with drillers who haven't claimed yet.
        // Check total_unclaimed BEFORE subtracting this driller's rewards_oil
        // to ensure fee is charged even if this is the only driller with unclaimed oil.
        if treasury.total_unclaimed > 0 {
            let fee = rewards_oil / 10;
            amount -= fee;
            treasury.driller_rewards_factor += Numeric::from_fraction(fee, treasury.total_unclaimed);
            treasury.total_refined += fee;
            self.lifetime_rewards_oil -= fee;
        }
        
        treasury.total_unclaimed -= rewards_oil;
        treasury.total_refined -= refined_oil;
        self.last_claim_oil_at = clock.unix_timestamp;

        amount
    }

    pub fn claim_sol(&mut self, clock: &Clock) -> u64 {
        let amount = self.rewards_sol;
        self.rewards_sol = 0;
        self.last_claim_sol_at = clock.unix_timestamp;
        amount
    }

    pub fn update_rewards(&mut self, treasury: &Treasury) {
        // Accumulate rewards, weighted by stake balance.
        if treasury.driller_rewards_factor > self.rewards_factor {
            let accumulated_rewards = treasury.driller_rewards_factor - self.rewards_factor;
            if accumulated_rewards < Numeric::ZERO {
                panic!("Accumulated rewards is negative");
            }
            let personal_rewards = accumulated_rewards * Numeric::from_u64(self.rewards_oil);
            self.refined_oil += personal_rewards.to_u64();
            self.lifetime_rewards_oil += personal_rewards.to_u64();
        }

        // Update this driller account's last seen rewards factor.
        self.rewards_factor = treasury.driller_rewards_factor;
    }
}

account!(OilAccount, Driller);