//! Benchmark report builder

use std::collections::HashMap;
use std::path::Path;
use std::time::Duration;

use super::super::cpu::CPUBenchmarkResult;
use super::super::disk::DiskBenchmarkResult;
use super::super::error::ProfilingError;
use super::super::memory::MemoryBandwidthResult;
use super::super::network::IperfResult;
use super::super::sanity;
use super::super::system::SystemProfile;
use super::super::validation;
use super::comparison::ComparisonResult;
use super::formats::{generate_html, generate_json, generate_text};
use uuid::Uuid;

/// Schema version for benchmark reports - increment when breaking changes are made
pub const SCHEMA_VERSION: &str = "1.0.0";

/// Aggregated benchmark report
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct BenchmarkReport {
    /// Schema version for compatibility checking with historical data
    #[serde(default = "default_schema_version")]
    pub schema_version: String,
    pub system_profile: Option<SystemProfile>,
    pub network_results: Vec<IperfResult>,
    pub disk_results: Vec<DiskBenchmarkResult>,
    pub memory_results: Vec<MemoryBandwidthResult>,
    pub cpu_results: Vec<CPUBenchmarkResult>,

    /// Timestamp when the benchmark was created (RFC3339 format)
    #[serde(default = "default_timestamp")]
    pub timestamp: String,
    pub duration: Duration,

    /// Unique identifier for this benchmark run
    #[serde(default = "default_benchmark_id")]
    pub benchmark_id: String,

    /// Environment identifier (e.g., "production", "staging", "dev")
    #[serde(default)]
    pub environment: Option<String>,

    /// Git commit hash or version identifier
    #[serde(default)]
    pub git_commit: Option<String>,

    /// Custom metadata tags for categorization and filtering
    #[serde(default)]
    pub tags: HashMap<String, String>,
}

fn default_schema_version() -> String {
    SCHEMA_VERSION.to_string()
}

fn default_timestamp() -> String {
    chrono::Utc::now().to_rfc3339()
}

fn default_benchmark_id() -> String {
    Uuid::new_v4().to_string()
}

impl Default for BenchmarkReport {
    fn default() -> Self {
        Self {
            schema_version: SCHEMA_VERSION.to_string(),
            system_profile: None,
            network_results: Vec::new(),
            disk_results: Vec::new(),
            memory_results: Vec::new(),
            cpu_results: Vec::new(),
            timestamp: chrono::Utc::now().to_rfc3339(),
            duration: Duration::default(),
            benchmark_id: Uuid::new_v4().to_string(),
            environment: None,
            git_commit: None,
            tags: HashMap::new(),
        }
    }
}

impl BenchmarkReport {
    /// Create new benchmark report
    pub fn new() -> Self {
        Self::default()
    }

    /// Add network result
    pub fn add_network_result(&mut self, result: IperfResult) -> Result<&mut Self, ProfilingError> {
        // Validate network result before adding
        sanity::SanityChecker::check_network_result(result.bandwidth_bps)?;

        // Check for duplicate entries
        if self.network_results.len() >= 100 {
            return Err(ProfilingError::InvalidConfig(
                "Too many network results (max 100)".to_string(),
            ));
        }

        self.network_results.push(result);
        Ok(self)
    }

    /// Add disk result
    pub fn add_disk_result(
        &mut self,
        result: DiskBenchmarkResult,
    ) -> Result<&mut Self, ProfilingError> {
        // Validate disk results
        sanity::SanityChecker::check_disk_result(result.sequential_read.throughput_mbps)?;
        sanity::SanityChecker::check_disk_result(result.sequential_write.throughput_mbps)?;
        sanity::SanityChecker::check_disk_result(result.random_read.throughput_mbps)?;
        sanity::SanityChecker::check_disk_result(result.random_write.throughput_mbps)?;
        sanity::SanityChecker::check_iops(result.iops.read_iops)?;
        sanity::SanityChecker::check_iops(result.iops.write_iops)?;

        // Check for duplicate entries
        if self.disk_results.len() >= 100 {
            return Err(ProfilingError::InvalidConfig(
                "Too many disk results (max 100)".to_string(),
            ));
        }

        self.disk_results.push(result);
        Ok(self)
    }

    /// Add memory result
    pub fn add_memory_result(
        &mut self,
        result: MemoryBandwidthResult,
    ) -> Result<&mut Self, ProfilingError> {
        // Validate memory results
        sanity::SanityChecker::check_memory_result(result.read_bandwidth_gbps)?;
        sanity::SanityChecker::check_memory_result(result.write_bandwidth_gbps)?;
        sanity::SanityChecker::check_memory_result(result.copy_bandwidth_gbps)?;

        // Check for duplicate entries
        if self.memory_results.len() >= 100 {
            return Err(ProfilingError::InvalidConfig(
                "Too many memory results (max 100)".to_string(),
            ));
        }

        self.memory_results.push(result);
        Ok(self)
    }

    /// Add CPU result
    pub fn add_cpu_result(
        &mut self,
        result: CPUBenchmarkResult,
    ) -> Result<&mut Self, ProfilingError> {
        // Validate CPU results
        sanity::SanityChecker::check_cpu_result(result.single_thread.combined_score)?;

        // Check for duplicate entries
        if self.cpu_results.len() >= 100 {
            return Err(ProfilingError::InvalidConfig(
                "Too many CPU results (max 100)".to_string(),
            ));
        }

        self.cpu_results.push(result);
        Ok(self)
    }

    /// Set system profile
    pub fn set_system_profile(&mut self, profile: SystemProfile) -> &mut Self {
        self.system_profile = Some(profile);
        self
    }

    /// Set environment identifier
    pub fn set_environment(&mut self, environment: impl Into<String>) -> &mut Self {
        self.environment = Some(environment.into());
        self
    }

    /// Set git commit hash or version
    pub fn set_git_commit(&mut self, commit: impl Into<String>) -> &mut Self {
        self.git_commit = Some(commit.into());
        self
    }

    /// Add a custom metadata tag
    pub fn add_tag(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
        self.tags.insert(key.into(), value.into());
        self
    }

    /// Set multiple tags at once
    pub fn set_tags(&mut self, tags: HashMap<String, String>) -> &mut Self {
        self.tags = tags;
        self
    }

    /// Generate JSON report
    pub fn generate_json(&self) -> Result<String, ProfilingError> {
        generate_json(self)
    }

    /// Generate HTML report
    pub fn generate_html(&self) -> Result<String, ProfilingError> {
        generate_html(self)
    }

    /// Generate text report
    pub fn generate_text(&self) -> Result<String, ProfilingError> {
        generate_text(self)
    }

    /// Export to CSV format
    pub fn export_to_csv(&self, path: &Path) -> Result<(), ProfilingError> {
        // Validate export path
        validation::validate_path(path)?;
        super::formats::export_to_csv(self, path)
    }

    /// Compare with baseline report
    pub fn compare_baselines(&self, baseline: &BenchmarkReport) -> ComparisonResult {
        super::comparison::compare_baselines(self, baseline)
    }

    /// Load report from JSON string
    pub fn from_json(json: &str) -> Result<Self, ProfilingError> {
        serde_json::from_str(json)
            .map_err(|e| ProfilingError::ParseError(format!("Failed to parse report JSON: {}", e)))
    }

    /// Load report from file
    pub fn from_file(path: &Path) -> Result<Self, ProfilingError> {
        validation::validate_file_path(path)?;
        let json = std::fs::read_to_string(path)
            .map_err(|e| ProfilingError::IoError(format!("Failed to read file: {}", e)))?;
        Self::from_json(&json)
    }

    /// Save report to JSON file
    pub fn save_to_file(&self, path: &Path) -> Result<(), ProfilingError> {
        let json = self.generate_json()?;
        std::fs::write(path, json)
            .map_err(|e| ProfilingError::IoError(format!("Failed to write file: {}", e)))
    }

    /// Convert to JSON string
    pub fn to_json(&self) -> Result<String, ProfilingError> {
        self.generate_json()
    }
}

/// Load report from JSON string
pub fn load_report_json(json: &str) -> Result<BenchmarkReport, ProfilingError> {
    BenchmarkReport::from_json(json)
}

/// Load report from file
pub fn load_report_file(path: &str) -> Result<BenchmarkReport, ProfilingError> {
    let path = std::path::Path::new(path);
    BenchmarkReport::from_file(path)
}

/// Save report to file
pub fn save_report_file(report: &BenchmarkReport, path: &str) -> Result<(), ProfilingError> {
    let path = std::path::Path::new(path);
    report.save_to_file(path)
}
