//! Decentralized Price Oracle Contract
//!
//! This contract provides manipulation-resistant price feeds by:
//! - Aggregating prices from multiple trusted reporters
//! - Using median aggregation to resist outliers
//! - Implementing TWAP (Time-Weighted Average Price)
//! - Detecting and rejecting stale prices
//!
//! # Architecture
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────┐
//! │                    ORACLE ARCHITECTURE                      │
//! ├─────────────────────────────────────────────────────────────┤
//! │                                                             │
//! │  REPORTERS (Off-chain price feeds):                        │
//! │  ┌────────┐  ┌────────┐  ┌────────┐  ┌────────┐           │
//! │  │Chainlink│  │  Pyth  │  │ Band   │  │ Custom │           │
//! │  │Reporter │  │Reporter│  │Reporter│  │Reporter│           │
//! │  └───┬────┘  └───┬────┘  └───┬────┘  └───┬────┘           │
//! │      │           │           │           │                  │
//! │      ▼           ▼           ▼           ▼                  │
//! │  ┌─────────────────────────────────────────────┐           │
//! │  │           MEDIAN AGGREGATION                │           │
//! │  │    [100, 101, 99, 102, 100] → 100          │           │
//! │  │    Rejects outliers automatically           │           │
//! │  └─────────────────────────────────────────────┘           │
//! │                          │                                  │
//! │                          ▼                                  │
//! │  ┌─────────────────────────────────────────────┐           │
//! │  │              TWAP CALCULATION               │           │
//! │  │    Accumulates prices over time             │           │
//! │  │    twap = Σ(price * time) / Σ(time)        │           │
//! │  └─────────────────────────────────────────────┘           │
//! │                          │                                  │
//! │                          ▼                                  │
//! │  ┌─────────────────────────────────────────────┐           │
//! │  │         CONSUMER CONTRACTS                  │           │
//! │  │    Perps, DEX, Lending, Options             │           │
//! │  └─────────────────────────────────────────────┘           │
//! └─────────────────────────────────────────────────────────────┘
//! ```

#![no_std]

extern crate alloc;

use alloc::string::String;
use alloc::vec::Vec;
use borsh::{BorshDeserialize, BorshSerialize};
use synor_sdk::prelude::*;
use synor_sdk::{require, require_auth};

// =============================================================================
// CONSTANTS
// =============================================================================

/// Price precision (6 decimals)
pub const PRICE_PRECISION: u64 = 1_000_000;

/// Maximum price staleness (5 minutes)
pub const MAX_STALENESS: u64 = 5 * 60;

/// Minimum reporters required for valid price
pub const MIN_REPORTERS: usize = 1;

/// TWAP observation window (1 hour)
pub const TWAP_WINDOW: u64 = 60 * 60;

/// Maximum TWAP observations to store
pub const MAX_TWAP_OBSERVATIONS: usize = 60;

/// Maximum deviation between reporters (5% = 500 bps)
pub const MAX_DEVIATION_BPS: u64 = 500;

/// Basis points denominator
pub const BPS_DENOMINATOR: u64 = 10000;

// =============================================================================
// EVENT TOPICS
// =============================================================================

fn event_topic(name: &[u8]) -> [u8; 32] {
    use synor_sdk::crypto::blake3_hash;
    blake3_hash(name).0
}

// =============================================================================
// STORAGE KEYS
// =============================================================================

mod keys {
    pub const OWNER: &[u8] = b"oracle:owner";
    pub const INITIALIZED: &[u8] = b"oracle:initialized";

    // Reporters
    pub const REPORTERS: &[u8] = b"oracle:reporters";
    pub const REPORTER_COUNT: &[u8] = b"oracle:reporter_count";

    // Price feeds
    pub const FEEDS: &[u8] = b"oracle:feeds";
    pub const FEED_COUNT: &[u8] = b"oracle:feed_count";

    // Prices
    pub const PRICES: &[u8] = b"oracle:prices";
    pub const REPORTER_PRICES: &[u8] = b"oracle:reporter_prices";

    // TWAP
    pub const TWAP_OBSERVATIONS: &[u8] = b"oracle:twap";
    pub const TWAP_INDEX: &[u8] = b"oracle:twap_idx";
}

// =============================================================================
// DATA STRUCTURES
// =============================================================================

/// Reporter status
#[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub enum ReporterStatus {
    Active,
    Suspended,
    Removed,
}

/// A trusted price reporter
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub struct Reporter {
    /// Reporter address
    pub address: Address,
    /// Reporter name/description
    pub name: String,
    /// Status
    pub status: ReporterStatus,
    /// Total reports submitted
    pub report_count: u64,
    /// Last report timestamp
    pub last_report: u64,
    /// When registered
    pub registered_at: u64,
}

/// Price feed configuration
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub struct PriceFeed {
    /// Feed ID
    pub id: u32,
    /// Base asset (e.g., "BTC")
    pub base_asset: String,
    /// Quote asset (e.g., "USD")
    pub quote_asset: String,
    /// Decimals (usually 6)
    pub decimals: u8,
    /// Minimum reporters required
    pub min_reporters: u8,
    /// Maximum staleness (seconds)
    pub max_staleness: u64,
    /// Whether feed is active
    pub is_active: bool,
    /// Created timestamp
    pub created_at: u64,
}

/// Individual price report from a reporter
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub struct PriceReport {
    /// Feed ID
    pub feed_id: u32,
    /// Reporter address
    pub reporter: Address,
    /// Price (in PRICE_PRECISION)
    pub price: u64,
    /// Confidence (lower = more confident)
    pub confidence: u64,
    /// Timestamp of report
    pub timestamp: u64,
}

/// Aggregated price for a feed
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub struct AggregatedPrice {
    /// Feed ID
    pub feed_id: u32,
    /// Aggregated price (median)
    pub price: u64,
    /// Number of reporters
    pub num_reporters: u8,
    /// Minimum reported price
    pub min_price: u64,
    /// Maximum reported price
    pub max_price: u64,
    /// Average confidence
    pub avg_confidence: u64,
    /// Aggregation timestamp
    pub timestamp: u64,
    /// Is price valid (enough reporters, not stale)
    pub is_valid: bool,
}

/// TWAP observation
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub struct TwapObservation {
    /// Price at this observation
    pub price: u64,
    /// Cumulative price * time
    pub cumulative: u128,
    /// Timestamp
    pub timestamp: u64,
}

// =============================================================================
// EVENTS
// =============================================================================

#[derive(BorshSerialize)]
pub struct ReporterRegistered {
    pub reporter: Address,
    pub name: String,
}

#[derive(BorshSerialize)]
pub struct PriceFeedCreated {
    pub feed_id: u32,
    pub base_asset: String,
    pub quote_asset: String,
}

#[derive(BorshSerialize)]
pub struct PriceUpdated {
    pub feed_id: u32,
    pub price: u64,
    pub num_reporters: u8,
    pub timestamp: u64,
}

#[derive(BorshSerialize)]
pub struct ReporterSuspended {
    pub reporter: Address,
    pub reason: String,
}

// =============================================================================
// STORAGE HELPERS
// =============================================================================

fn get_owner() -> Option<Address> {
    storage::get::<Address>(keys::OWNER)
}

fn is_owner(addr: &Address) -> bool {
    get_owner().map(|o| o == *addr).unwrap_or(false)
}

fn is_initialized() -> bool {
    storage::get::<bool>(keys::INITIALIZED).unwrap_or(false)
}

fn get_reporter_count() -> u32 {
    storage::get::<u32>(keys::REPORTER_COUNT).unwrap_or(0)
}

fn set_reporter_count(count: u32) {
    storage::set(keys::REPORTER_COUNT, &count);
}

fn get_reporter(addr: &Address) -> Option<Reporter> {
    storage::get_with_suffix::<Reporter>(keys::REPORTERS, addr.as_bytes())
}

fn set_reporter(addr: &Address, reporter: &Reporter) {
    storage::set_with_suffix(keys::REPORTERS, addr.as_bytes(), reporter);
}

fn get_feed_count() -> u32 {
    storage::get::<u32>(keys::FEED_COUNT).unwrap_or(0)
}

fn set_feed_count(count: u32) {
    storage::set(keys::FEED_COUNT, &count);
}

fn get_feed(id: u32) -> Option<PriceFeed> {
    storage::get_with_suffix::<PriceFeed>(keys::FEEDS, &id.to_le_bytes())
}

fn set_feed(id: u32, feed: &PriceFeed) {
    storage::set_with_suffix(keys::FEEDS, &id.to_le_bytes(), feed);
}

fn get_aggregated_price(feed_id: u32) -> Option<AggregatedPrice> {
    storage::get_with_suffix::<AggregatedPrice>(keys::PRICES, &feed_id.to_le_bytes())
}

fn set_aggregated_price(feed_id: u32, price: &AggregatedPrice) {
    storage::set_with_suffix(keys::PRICES, &feed_id.to_le_bytes(), price);
}

fn get_reporter_price(feed_id: u32, reporter: &Address) -> Option<PriceReport> {
    let mut key = feed_id.to_le_bytes().to_vec();
    key.extend_from_slice(reporter.as_bytes());
    storage::get_with_suffix::<PriceReport>(keys::REPORTER_PRICES, &key)
}

fn set_reporter_price(feed_id: u32, reporter: &Address, report: &PriceReport) {
    let mut key = feed_id.to_le_bytes().to_vec();
    key.extend_from_slice(reporter.as_bytes());
    storage::set_with_suffix(keys::REPORTER_PRICES, &key, report);
}

fn get_twap_index(feed_id: u32) -> usize {
    storage::get_with_suffix::<usize>(keys::TWAP_INDEX, &feed_id.to_le_bytes()).unwrap_or(0)
}

fn set_twap_index(feed_id: u32, index: usize) {
    storage::set_with_suffix(keys::TWAP_INDEX, &feed_id.to_le_bytes(), &index);
}

fn get_twap_observation(feed_id: u32, index: usize) -> Option<TwapObservation> {
    let mut key = feed_id.to_le_bytes().to_vec();
    key.extend_from_slice(&(index as u32).to_le_bytes());
    storage::get_with_suffix::<TwapObservation>(keys::TWAP_OBSERVATIONS, &key)
}

fn set_twap_observation(feed_id: u32, index: usize, obs: &TwapObservation) {
    let mut key = feed_id.to_le_bytes().to_vec();
    key.extend_from_slice(&(index as u32).to_le_bytes());
    storage::set_with_suffix(keys::TWAP_OBSERVATIONS, &key, obs);
}

// =============================================================================
// CALCULATION HELPERS
// =============================================================================

/// Calculate median of prices (manipulation resistant)
fn calculate_median(prices: &mut [u64]) -> u64 {
    if prices.is_empty() {
        return 0;
    }

    prices.sort_unstable();
    let len = prices.len();

    if len % 2 == 0 {
        (prices[len / 2 - 1] + prices[len / 2]) / 2
    } else {
        prices[len / 2]
    }
}

/// Check if price deviation is within acceptable range
fn is_deviation_acceptable(prices: &[u64]) -> bool {
    if prices.is_empty() {
        return false;
    }

    let min = *prices.iter().min().unwrap();
    let max = *prices.iter().max().unwrap();

    if min == 0 {
        return false;
    }

    let deviation_bps = ((max - min) as u128 * BPS_DENOMINATOR as u128) / min as u128;
    deviation_bps <= MAX_DEVIATION_BPS as u128
}

// =============================================================================
// ENTRY POINTS
// =============================================================================

synor_sdk::entry_point!(init, call);

fn init(params: &[u8]) -> Result<()> {
    require!(!is_initialized(), Error::invalid_args("Already initialized"));

    // No params needed, just set owner
    let _ = params;

    storage::set(keys::OWNER, &caller());
    storage::set(keys::INITIALIZED, &true);
    set_reporter_count(0);
    set_feed_count(0);

    Ok(())
}

fn call(selector: &[u8], params: &[u8]) -> Result<Vec<u8>> {
    // Method selectors
    let register_reporter_sel = synor_sdk::method_selector("register_reporter");
    let suspend_reporter_sel = synor_sdk::method_selector("suspend_reporter");
    let create_feed_sel = synor_sdk::method_selector("create_feed");
    let report_price_sel = synor_sdk::method_selector("report_price");
    let report_prices_batch_sel = synor_sdk::method_selector("report_prices_batch");
    let get_price_sel = synor_sdk::method_selector("get_price");
    let get_twap_sel = synor_sdk::method_selector("get_twap");
    let get_feed_sel = synor_sdk::method_selector("get_feed");
    let get_reporter_sel = synor_sdk::method_selector("get_reporter");
    let is_price_valid_sel = synor_sdk::method_selector("is_price_valid");

    match selector {
        // ===== Admin Methods =====

        s if s == register_reporter_sel => {
            let owner = get_owner().ok_or(Error::Unauthorized)?;
            require_auth!(owner);

            #[derive(BorshDeserialize)]
            struct Args {
                reporter: Address,
                name: String,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Invalid register_reporter params"))?;

            require!(get_reporter(&args.reporter).is_none(),
                Error::invalid_args("Reporter already registered"));

            let reporter = Reporter {
                address: args.reporter,
                name: args.name.clone(),
                status: ReporterStatus::Active,
                report_count: 0,
                last_report: 0,
                registered_at: timestamp(),
            };

            set_reporter(&args.reporter, &reporter);
            set_reporter_count(get_reporter_count() + 1);

            emit_raw(
                &[event_topic(b"ReporterRegistered")],
                &borsh::to_vec(&ReporterRegistered {
                    reporter: args.reporter,
                    name: args.name,
                }).unwrap(),
            );

            Ok(borsh::to_vec(&true).unwrap())
        }

        s if s == suspend_reporter_sel => {
            let owner = get_owner().ok_or(Error::Unauthorized)?;
            require_auth!(owner);

            #[derive(BorshDeserialize)]
            struct Args {
                reporter: Address,
                reason: String,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Invalid suspend_reporter params"))?;

            let mut reporter = get_reporter(&args.reporter)
                .ok_or_else(|| Error::invalid_args("Reporter not found"))?;

            reporter.status = ReporterStatus::Suspended;
            set_reporter(&args.reporter, &reporter);

            emit_raw(
                &[event_topic(b"ReporterSuspended")],
                &borsh::to_vec(&ReporterSuspended {
                    reporter: args.reporter,
                    reason: args.reason,
                }).unwrap(),
            );

            Ok(borsh::to_vec(&true).unwrap())
        }

        s if s == create_feed_sel => {
            let owner = get_owner().ok_or(Error::Unauthorized)?;
            require_auth!(owner);

            #[derive(BorshDeserialize)]
            struct Args {
                base_asset: String,
                quote_asset: String,
                decimals: u8,
                min_reporters: u8,
                max_staleness: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Invalid create_feed params"))?;

            let feed_id = get_feed_count();
            let feed = PriceFeed {
                id: feed_id,
                base_asset: args.base_asset.clone(),
                quote_asset: args.quote_asset.clone(),
                decimals: args.decimals,
                min_reporters: args.min_reporters.max(1),
                max_staleness: args.max_staleness.max(60), // min 60 seconds
                is_active: true,
                created_at: timestamp(),
            };

            set_feed(feed_id, &feed);
            set_feed_count(feed_id + 1);

            emit_raw(
                &[event_topic(b"PriceFeedCreated")],
                &borsh::to_vec(&PriceFeedCreated {
                    feed_id,
                    base_asset: args.base_asset,
                    quote_asset: args.quote_asset,
                }).unwrap(),
            );

            Ok(borsh::to_vec(&feed_id).unwrap())
        }

        // ===== Reporter Methods =====

        s if s == report_price_sel => {
            let reporter_addr = caller();
            let reporter = get_reporter(&reporter_addr)
                .ok_or_else(|| Error::invalid_args("Not a registered reporter"))?;

            require!(reporter.status == ReporterStatus::Active,
                Error::invalid_args("Reporter not active"));

            #[derive(BorshDeserialize)]
            struct Args {
                feed_id: u32,
                price: u64,
                confidence: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Invalid report_price params"))?;

            let feed = get_feed(args.feed_id)
                .ok_or_else(|| Error::invalid_args("Feed not found"))?;
            require!(feed.is_active, Error::invalid_args("Feed not active"));

            let now = timestamp();

            // Store individual reporter price
            let report = PriceReport {
                feed_id: args.feed_id,
                reporter: reporter_addr,
                price: args.price,
                confidence: args.confidence,
                timestamp: now,
            };
            set_reporter_price(args.feed_id, &reporter_addr, &report);

            // Update reporter stats
            let mut reporter = reporter;
            reporter.report_count += 1;
            reporter.last_report = now;
            set_reporter(&reporter_addr, &reporter);

            // Aggregate all reporter prices for this feed
            aggregate_prices(args.feed_id, &feed)?;

            Ok(borsh::to_vec(&true).unwrap())
        }

        s if s == report_prices_batch_sel => {
            let reporter_addr = caller();
            let reporter = get_reporter(&reporter_addr)
                .ok_or_else(|| Error::invalid_args("Not a registered reporter"))?;

            require!(reporter.status == ReporterStatus::Active,
                Error::invalid_args("Reporter not active"));

            #[derive(BorshDeserialize)]
            struct PriceInput {
                feed_id: u32,
                price: u64,
                confidence: u64,
            }

            #[derive(BorshDeserialize)]
            struct Args {
                prices: Vec<PriceInput>,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Invalid report_prices_batch params"))?;

            let now = timestamp();
            let mut updated_feeds = Vec::new();

            for input in &args.prices {
                let feed = match get_feed(input.feed_id) {
                    Some(f) if f.is_active => f,
                    _ => continue,
                };

                let report = PriceReport {
                    feed_id: input.feed_id,
                    reporter: reporter_addr,
                    price: input.price,
                    confidence: input.confidence,
                    timestamp: now,
                };
                set_reporter_price(input.feed_id, &reporter_addr, &report);

                // Aggregate prices for this feed
                if aggregate_prices(input.feed_id, &feed).is_ok() {
                    updated_feeds.push(input.feed_id);
                }
            }

            // Update reporter stats
            let mut reporter = reporter;
            reporter.report_count += args.prices.len() as u64;
            reporter.last_report = now;
            set_reporter(&reporter_addr, &reporter);

            Ok(borsh::to_vec(&updated_feeds).unwrap())
        }

        // ===== Query Methods =====

        s if s == get_price_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                feed_id: u32,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (feed_id: u32)"))?;

            let price = get_aggregated_price(args.feed_id);
            Ok(borsh::to_vec(&price).unwrap())
        }

        s if s == get_twap_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                feed_id: u32,
                window: u64, // Time window in seconds
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (feed_id: u32, window: u64)"))?;

            let twap = calculate_twap(args.feed_id, args.window);
            Ok(borsh::to_vec(&twap).unwrap())
        }

        s if s == get_feed_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                feed_id: u32,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (feed_id: u32)"))?;

            let feed = get_feed(args.feed_id);
            Ok(borsh::to_vec(&feed).unwrap())
        }

        s if s == get_reporter_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                reporter: Address,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (reporter: Address)"))?;

            let reporter = get_reporter(&args.reporter);
            Ok(borsh::to_vec(&reporter).unwrap())
        }

        s if s == is_price_valid_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                feed_id: u32,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (feed_id: u32)"))?;

            let valid = match get_aggregated_price(args.feed_id) {
                Some(price) => {
                    let feed = get_feed(args.feed_id);
                    let max_stale = feed.map(|f| f.max_staleness).unwrap_or(MAX_STALENESS);
                    price.is_valid && (timestamp() - price.timestamp) < max_stale
                }
                None => false,
            };

            Ok(borsh::to_vec(&valid).unwrap())
        }

        _ => Err(Error::InvalidMethod),
    }
}

// =============================================================================
// AGGREGATION LOGIC
// =============================================================================

/// Aggregate prices from all reporters for a feed
fn aggregate_prices(feed_id: u32, feed: &PriceFeed) -> Result<()> {
    let now = timestamp();
    let reporter_count = get_reporter_count();

    // Collect all valid reporter prices
    let mut prices: Vec<u64> = Vec::new();
    let mut confidences: Vec<u64> = Vec::new();

    // We need to iterate through reporters and collect their prices
    // In a real implementation, we'd have a list of reporter addresses
    // For now, we check the last few potential reporters
    for i in 0..reporter_count.min(100) {
        // Try to get reporter by index (simplified)
        // In production, maintain a list of reporter addresses
        let reporter_key = [keys::REPORTERS, &i.to_le_bytes()].concat();
        if let Some(reporter_data) = storage::get_raw(&reporter_key) {
            if let Ok(reporter) = Reporter::try_from_slice(&reporter_data) {
                if reporter.status == ReporterStatus::Active {
                    if let Some(report) = get_reporter_price(feed_id, &reporter.address) {
                        // Check freshness
                        if now - report.timestamp < feed.max_staleness {
                            prices.push(report.price);
                            confidences.push(report.confidence);
                        }
                    }
                }
            }
        }
    }

    // Also check if there's a direct report from caller
    if let Some(report) = get_reporter_price(feed_id, &caller()) {
        if now - report.timestamp < feed.max_staleness {
            if !prices.contains(&report.price) {
                prices.push(report.price);
                confidences.push(report.confidence);
            }
        }
    }

    // Need minimum reporters
    let is_valid = prices.len() >= feed.min_reporters as usize
        && !prices.is_empty()
        && is_deviation_acceptable(&prices);

    let (median_price, min_price, max_price) = if prices.is_empty() {
        (0, 0, 0)
    } else {
        let min = *prices.iter().min().unwrap();
        let max = *prices.iter().max().unwrap();
        let median = calculate_median(&mut prices);
        (median, min, max)
    };

    let avg_confidence = if confidences.is_empty() {
        0
    } else {
        confidences.iter().sum::<u64>() / confidences.len() as u64
    };

    let aggregated = AggregatedPrice {
        feed_id,
        price: median_price,
        num_reporters: prices.len() as u8,
        min_price,
        max_price,
        avg_confidence,
        timestamp: now,
        is_valid,
    };

    set_aggregated_price(feed_id, &aggregated);

    // Update TWAP observation
    if is_valid && median_price > 0 {
        update_twap(feed_id, median_price, now);
    }

    emit_raw(
        &[event_topic(b"PriceUpdated")],
        &borsh::to_vec(&PriceUpdated {
            feed_id,
            price: median_price,
            num_reporters: prices.len() as u8,
            timestamp: now,
        }).unwrap(),
    );

    Ok(())
}

/// Update TWAP observation
fn update_twap(feed_id: u32, price: u64, now: u64) {
    let current_index = get_twap_index(feed_id);
    let prev_obs = get_twap_observation(feed_id, current_index);

    let cumulative = match prev_obs {
        Some(obs) => {
            let time_delta = now.saturating_sub(obs.timestamp);
            obs.cumulative + (obs.price as u128 * time_delta as u128)
        }
        None => 0,
    };

    let new_obs = TwapObservation {
        price,
        cumulative,
        timestamp: now,
    };

    let next_index = (current_index + 1) % MAX_TWAP_OBSERVATIONS;
    set_twap_observation(feed_id, next_index, &new_obs);
    set_twap_index(feed_id, next_index);
}

/// Calculate TWAP for a given window
fn calculate_twap(feed_id: u32, window: u64) -> Option<u64> {
    let current_index = get_twap_index(feed_id);
    let current_obs = get_twap_observation(feed_id, current_index)?;

    let now = timestamp();
    let window_start = now.saturating_sub(window);

    // Find oldest observation within window
    let mut oldest_index = current_index;
    let mut oldest_obs = current_obs.clone();

    for i in 1..MAX_TWAP_OBSERVATIONS {
        let idx = (current_index + MAX_TWAP_OBSERVATIONS - i) % MAX_TWAP_OBSERVATIONS;
        if let Some(obs) = get_twap_observation(feed_id, idx) {
            if obs.timestamp >= window_start {
                oldest_index = idx;
                oldest_obs = obs;
            } else {
                break;
            }
        } else {
            break;
        }
    }

    if oldest_index == current_index {
        // Only one observation, return current price
        return Some(current_obs.price);
    }

    let time_delta = current_obs.timestamp.saturating_sub(oldest_obs.timestamp);
    if time_delta == 0 {
        return Some(current_obs.price);
    }

    // Add final price * time_since_last_update
    let time_since_last = now.saturating_sub(current_obs.timestamp);
    let cumulative_delta = current_obs.cumulative - oldest_obs.cumulative
        + (current_obs.price as u128 * time_since_last as u128);

    let total_time = time_delta + time_since_last;
    if total_time == 0 {
        return Some(current_obs.price);
    }

    Some((cumulative_delta / total_time as u128) as u64)
}
