use std::collections::HashMap;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SlotStatus {
    FirstShredReceived,
    CreatedBank,
    Completed,
    Processed,
    Confirmed,
    Finalized,
    Dead,
}

impl SlotStatus {
    pub fn from_proto(status: i32) -> Option<Self> {
        match status {
            0 => Some(SlotStatus::Processed),
            1 => Some(SlotStatus::Confirmed),
            2 => Some(SlotStatus::Finalized),
            3 => Some(SlotStatus::FirstShredReceived),
            4 => Some(SlotStatus::Completed),
            5 => Some(SlotStatus::CreatedBank),
            6 => Some(SlotStatus::Dead),
            _ => None,
        }
    }

    pub fn order(&self) -> u8 {
        match self {
            SlotStatus::FirstShredReceived => 0,
            SlotStatus::CreatedBank => 1,
            SlotStatus::Completed => 2,
            SlotStatus::Processed => 3,
            SlotStatus::Confirmed => 4,
            SlotStatus::Finalized => 5,
            SlotStatus::Dead => 6,
        }
    }

    pub fn required_stages() -> &'static [SlotStatus] {
        &[
            SlotStatus::FirstShredReceived,
            SlotStatus::Completed,
            SlotStatus::CreatedBank,
            SlotStatus::Processed,
            SlotStatus::Confirmed,
            SlotStatus::Finalized,
        ]
    }
}

#[derive(Debug, Clone)]
pub struct SlotUpdate {
    pub slot: u64,
    pub status: SlotStatus,
    pub timestamp: Instant,
    pub wall_time: SystemTime,
    pub parent: Option<u64>,
}

#[derive(Debug, Clone)]
pub struct AccountUpdate {
    pub slot: u64,
    pub pubkey: String,
    pub write_version: u64,
    pub txn_signature: Option<String>,
    pub timestamp: Instant,
    pub wall_time: SystemTime,
}

#[derive(Debug, Clone, Default)]
pub struct SlotTimings {
    pub first_shred_received: Option<Instant>,
    pub created_bank: Option<Instant>,
    pub completed: Option<Instant>,
    pub processed: Option<Instant>,
    pub confirmed: Option<Instant>,
    pub finalized: Option<Instant>,
}

impl SlotTimings {
    pub fn get(&self, status: SlotStatus) -> Option<Instant> {
        match status {
            SlotStatus::FirstShredReceived => self.first_shred_received,
            SlotStatus::CreatedBank => self.created_bank,
            SlotStatus::Completed => self.completed,
            SlotStatus::Processed => self.processed,
            SlotStatus::Confirmed => self.confirmed,
            SlotStatus::Finalized => self.finalized,
            SlotStatus::Dead => None,
        }
    }

    pub fn set(&mut self, status: SlotStatus, timestamp: Instant) {
        match status {
            SlotStatus::FirstShredReceived => self.first_shred_received = Some(timestamp),
            SlotStatus::CreatedBank => self.created_bank = Some(timestamp),
            SlotStatus::Completed => self.completed = Some(timestamp),
            SlotStatus::Processed => self.processed = Some(timestamp),
            SlotStatus::Confirmed => self.confirmed = Some(timestamp),
            SlotStatus::Finalized => self.finalized = Some(timestamp),
            SlotStatus::Dead => {}
        }
    }

    pub fn has_all_stages(&self) -> bool {
        self.first_shred_received.is_some()
            && self.created_bank.is_some()
            && self.completed.is_some()
            && self.processed.is_some()
            && self.confirmed.is_some()
            && self.finalized.is_some()
    }

    pub fn download_time(&self) -> Option<Duration> {
        match (self.first_shred_received, self.completed) {
            (Some(start), Some(end)) => Some(end.duration_since(start)),
            _ => None,
        }
    }

    pub fn replay_time(&self) -> Option<Duration> {
        match (self.created_bank, self.processed) {
            (Some(start), Some(end)) => Some(end.duration_since(start)),
            _ => None,
        }
    }

    pub fn confirm_time(&self) -> Option<Duration> {
        match (self.processed, self.confirmed) {
            (Some(start), Some(end)) => Some(end.duration_since(start)),
            _ => None,
        }
    }

    pub fn finalize_time(&self) -> Option<Duration> {
        match (self.confirmed, self.finalized) {
            (Some(start), Some(end)) => Some(end.duration_since(start)),
            _ => None,
        }
    }
}

#[derive(Debug, Clone, Default)]
pub struct EndpointData {
    pub name: String,
    pub slot_updates: Vec<SlotUpdate>,
    pub account_updates: Vec<AccountUpdate>,
    pub slot_timings: HashMap<u64, SlotTimings>,
}

impl EndpointData {
    pub fn new(name: String) -> Self {
        Self {
            name,
            slot_updates: Vec::with_capacity(10000),
            account_updates: Vec::with_capacity(50000),
            slot_timings: HashMap::with_capacity(2000),
        }
    }

    pub fn with_capacity(name: String, slot_count: usize, buffer_pct: f32) -> Self {
        let capacity = ((slot_count as f32 * (1.0 + buffer_pct)) as usize) * 6;
        let acct_capacity = capacity * 100;
        Self {
            name,
            slot_updates: Vec::with_capacity(capacity),
            account_updates: Vec::with_capacity(acct_capacity),
            slot_timings: HashMap::with_capacity(slot_count),
        }
    }

    pub fn add_slot_update(&mut self, update: SlotUpdate) {
        let slot = update.slot;
        let status = update.status;
        let timestamp = update.timestamp;
        self.slot_updates.push(update);
        self.slot_timings
            .entry(slot)
            .or_default()
            .set(status, timestamp);
    }

    pub fn add_account_update(&mut self, update: AccountUpdate) {
        self.account_updates.push(update);
    }

    pub fn complete_slots(&self) -> Vec<u64> {
        self.slot_timings
            .iter()
            .filter(|(_, t)| t.has_all_stages())
            .map(|(s, _)| *s)
            .collect()
    }

    pub fn unique_slot_count(&self) -> usize {
        self.slot_timings.len()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderInfo {
    pub url: String,
    pub plugin_name: String,
    pub plugin_version: String,
    pub avg_ping_ms: f64,
    pub total_updates: u64,
    pub unique_slots: u64,
    pub account_count: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Percentiles {
    pub p50: f64,
    pub p90: f64,
    pub p99: f64,
}

impl Percentiles {
    pub fn from_durations(mut values: Vec<Duration>) -> Self {
        if values.is_empty() {
            return Self { p50: 0.0, p90: 0.0, p99: 0.0 };
        }
        values.sort_unstable();
        let len = values.len();
        Self {
            p50: values[len.saturating_sub(1).min(len * 50 / 100)].as_secs_f64() * 1000.0,
            p90: values[len.saturating_sub(1).min(len * 90 / 100)].as_secs_f64() * 1000.0,
            p99: values[len.saturating_sub(1).min(len * 99 / 100)].as_secs_f64() * 1000.0,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderMetrics {
    pub first_shred_delay: Percentiles,
    pub processing_delay: Percentiles,
    pub confirmation_delay: Percentiles,
    pub finalization_delay: Percentiles,
    pub download_time: Percentiles,
    pub replay_time: Percentiles,
    pub confirm_time: Percentiles,
    pub finalize_time: Percentiles,
    pub account_delay: Option<Percentiles>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StageDurations {
    pub download_ms: f64,
    pub replay_ms: f64,
    pub confirm_ms: f64,
    pub finalize_ms: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlotTransition {
    pub status: String,
    pub timestamp_ms: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountDetail {
    pub pubkey: String,
    pub write_version: u64,
    pub signature: String,
    pub delay_ms: Option<f64>,
    pub timestamp_ms: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlotDetail {
    pub first_shred_delay_ms: Option<f64>,
    pub processing_delay_ms: Option<f64>,
    pub confirmation_delay_ms: Option<f64>,
    pub finalization_delay_ms: Option<f64>,
    pub transitions: Vec<SlotTransition>,
    pub durations: StageDurations,
    pub accounts: Vec<AccountDetail>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlotComparison {
    pub slot: u64,
    pub provider1: SlotDetail,
    pub provider2: SlotDetail,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkMeta {
    pub total_slots: u64,
    pub common_slots: u64,
    pub compared_slots: u64,
    pub dropped_slots: u64,
    pub duration_ms: u64,
    pub started_at: u64,
    pub account_totals: Option<(u64, u64)>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeyserBenchmarkResult {
    pub version: String,
    pub with_accounts: bool,
    pub account_filter: Option<String>,
    pub grpc_settings: super::config::GrpcSettingsSummary,
    pub meta: BenchmarkMeta,
    pub providers: [ProviderInfo; 2],
    pub provider1_metrics: ProviderMetrics,
    pub provider2_metrics: ProviderMetrics,
    pub slots: Vec<SlotComparison>,
}

pub fn to_epoch_ms(time: SystemTime) -> u64 {
    time.duration_since(UNIX_EPOCH).unwrap_or_default().as_millis() as u64
}
