use leptos::prelude::*;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{HtmlCanvasElement, HtmlAnchorElement, Blob, Url, window, document};
use chrono::{DateTime, Utc};
use std::collections::HashMap;

use crate::services::dashboard_config::DashboardConfig;
use crate::components::charts::ChartData;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ExportFormat {
    Png,
    Jpeg,
    Pdf,
    Svg,
    Csv,
    Excel,
    Json,
}

impl ExportFormat {
    pub fn extension(&self) -> &'static str {
        match self {
            ExportFormat::Png => "png",
            ExportFormat::Jpeg => "jpeg",
            ExportFormat::Pdf => "pdf",
            ExportFormat::Svg => "svg",
            ExportFormat::Csv => "csv",
            ExportFormat::Excel => "xlsx",
            ExportFormat::Json => "json",
        }
    }

    pub fn mime_type(&self) -> &'static str {
        match self {
            ExportFormat::Png => "image/png",
            ExportFormat::Jpeg => "image/jpeg",
            ExportFormat::Pdf => "application/pdf",
            ExportFormat::Svg => "image/svg+xml",
            ExportFormat::Csv => "text/csv",
            ExportFormat::Excel => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            ExportFormat::Json => "application/json",
        }
    }

    pub fn display_name(&self) -> &'static str {
        match self {
            ExportFormat::Png => "PNG Image",
            ExportFormat::Jpeg => "JPEG Image",
            ExportFormat::Pdf => "PDF Document",
            ExportFormat::Svg => "SVG Vector",
            ExportFormat::Csv => "CSV Data",
            ExportFormat::Excel => "Excel Spreadsheet",
            ExportFormat::Json => "JSON Data",
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportOptions {
    pub format: ExportFormat,
    pub filename: Option<String>,
    pub include_metadata: bool,
    pub compress: bool,
    pub quality: Option<f64>, // For image formats (0.0-1.0)
    pub width: Option<u32>,
    pub height: Option<u32>,
    pub background_color: Option<String>,
    pub scale: Option<f64>,
}

impl Default for ExportOptions {
    fn default() -> Self {
        Self {
            format: ExportFormat::Png,
            filename: None,
            include_metadata: true,
            compress: false,
            quality: Some(0.95),
            width: None,
            height: None,
            background_color: Some("#ffffff".to_string()),
            scale: Some(2.0), // High DPI
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportMetadata {
    pub exported_at: DateTime<Utc>,
    pub dashboard_name: String,
    pub user_id: Option<String>,
    pub widget_count: usize,
    pub export_format: ExportFormat,
    pub version: String,
}

#[derive(Debug, Clone)]
pub struct ExportService {
    export_history: RwSignal<Vec<ExportRecord>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportRecord {
    pub id: String,
    pub filename: String,
    pub format: ExportFormat,
    pub size_bytes: usize,
    pub exported_at: DateTime<Utc>,
    pub success: bool,
    pub error_message: Option<String>,
}

impl ExportService {
    pub fn new() -> Self {
        Self {
            export_history: RwSignal::new(Vec::new()),
        }
    }

    pub async fn export_dashboard(
        &self,
        config: &DashboardConfig,
        format: ExportFormat,
    ) -> Result<(), String> {
        let options = ExportOptions {
            format: format.clone(),
            filename: Some(format!("{}_dashboard", config.name.replace(" ", "_").to_lowercase())),
            ..Default::default()
        };

        self.export_dashboard_with_options(config, options).await
    }

    pub async fn export_dashboard_with_options(
        &self,
        config: &DashboardConfig,
        options: ExportOptions,
    ) -> Result<(), String> {
        let start_time = Utc::now();
        let export_id = uuid::Uuid::new_v4().to_string();

        match options.format {
            ExportFormat::Json => {
                self.export_dashboard_json(config, &options, &export_id).await
            },
            ExportFormat::Csv => {
                self.export_dashboard_csv(config, &options, &export_id).await
            },
            ExportFormat::Png | ExportFormat::Jpeg => {
                self.export_dashboard_image(config, &options, &export_id).await
            },
            ExportFormat::Pdf => {
                self.export_dashboard_pdf(config, &options, &export_id).await
            },
            ExportFormat::Svg => {
                self.export_dashboard_svg(config, &options, &export_id).await
            },
            ExportFormat::Excel => {
                self.export_dashboard_excel(config, &options, &export_id).await
            },
        }
    }

    pub async fn export_chart(
        &self,
        canvas: &HtmlCanvasElement,
        chart_title: &str,
        format: ExportFormat,
    ) -> Result<(), String> {
        let options = ExportOptions {
            format: format.clone(),
            filename: Some(format!("{}_chart", chart_title.replace(" ", "_").to_lowercase())),
            ..Default::default()
        };

        self.export_chart_with_options(canvas, chart_title, options).await
    }

    pub async fn export_chart_with_options(
        &self,
        canvas: &HtmlCanvasElement,
        chart_title: &str,
        options: ExportOptions,
    ) -> Result<(), String> {
        match options.format {
            ExportFormat::Png | ExportFormat::Jpeg => {
                self.export_canvas_as_image(canvas, &options).await
            },
            ExportFormat::Pdf => {
                self.export_canvas_as_pdf(canvas, chart_title, &options).await
            },
            ExportFormat::Svg => {
                Err("SVG export not supported for canvas charts".to_string())
            },
            _ => {
                Err(format!("Format {:?} not supported for chart export", options.format))
            }
        }
    }

    async fn export_dashboard_json(
        &self,
        config: &DashboardConfig,
        options: &ExportOptions,
        export_id: &str,
    ) -> Result<(), String> {
        let mut export_data = serde_json::to_value(config)
            .map_err(|e| format!("Failed to serialize dashboard config: {}", e))?;

        if options.include_metadata {
            let metadata = ExportMetadata {
                exported_at: Utc::now(),
                dashboard_name: config.name.clone(),
                user_id: config.user_id.clone(),
                widget_count: config.widgets.len(),
                export_format: options.format.clone(),
                version: env!("CARGO_PKG_VERSION").to_string(),
            };

            export_data["export_metadata"] = serde_json::to_value(metadata)
                .map_err(|e| format!("Failed to serialize metadata: {}", e))?;
        }

        let json_string = if options.compress {
            serde_json::to_string(&export_data)
        } else {
            serde_json::to_string_pretty(&export_data)
        }.map_err(|e| format!("Failed to serialize export data: {}", e))?;

        let filename = generate_filename(options, "dashboard");
        self.download_text_file(&json_string, &filename, options.format.mime_type())?;

        self.record_export(export_id, &filename, options.format.clone(), json_string.len(), true, None);
        Ok(())
    }

    async fn export_dashboard_csv(
        &self,
        config: &DashboardConfig,
        options: &ExportOptions,
        export_id: &str,
    ) -> Result<(), String> {
        let mut csv_content = String::new();
        csv_content.push_str("Widget ID,Widget Type,Title,Position X,Position Y,Width,Height,Data Source,Refresh Interval\n");

        for (id, widget) in &config.widgets {
            csv_content.push_str(&format!(
                "{},{:?},{},{},{},{},{},{},{}\n",
                id,
                widget.widget_type,
                escape_csv_field(&widget.title),
                widget.position.x,
                widget.position.y,
                widget.size.width,
                widget.size.height,
                escape_csv_field(&widget.data_source),
                widget.refresh_interval
            ));
        }

        let filename = generate_filename(options, "dashboard");
        self.download_text_file(&csv_content, &filename, options.format.mime_type())?;

        self.record_export(export_id, &filename, options.format.clone(), csv_content.len(), true, None);
        Ok(())
    }

    async fn export_dashboard_image(
        &self,
        config: &DashboardConfig,
        options: &ExportOptions,
        export_id: &str,
    ) -> Result<(), String> {
        // Create a composite image of the dashboard
        let canvas = self.create_dashboard_canvas(config, options)?;
        self.export_canvas_as_image(&canvas, options).await?;

        let filename = generate_filename(options, "dashboard");
        self.record_export(export_id, &filename, options.format.clone(), 0, true, None);
        Ok(())
    }

    async fn export_dashboard_pdf(
        &self,
        config: &DashboardConfig,
        options: &ExportOptions,
        export_id: &str,
    ) -> Result<(), String> {
        // Create PDF using jsPDF or similar library
        // This is a placeholder implementation
        let pdf_content = format!("Dashboard PDF Export\n\nDashboard: {}\nWidgets: {}\nExported: {}",
            config.name,
            config.widgets.len(),
            Utc::now().format("%Y-%m-%d %H:%M:%S"));

        let filename = generate_filename(options, "dashboard");
        self.download_text_file(&pdf_content, &filename, options.format.mime_type())?;

        self.record_export(export_id, &filename, options.format.clone(), pdf_content.len(), true, None);
        Ok(())
    }

    async fn export_dashboard_svg(
        &self,
        config: &DashboardConfig,
        options: &ExportOptions,
        export_id: &str,
    ) -> Result<(), String> {
        // Generate SVG representation of the dashboard
        let svg_content = self.generate_dashboard_svg(config, options)?;

        let filename = generate_filename(options, "dashboard");
        self.download_text_file(&svg_content, &filename, options.format.mime_type())?;

        self.record_export(export_id, &filename, options.format.clone(), svg_content.len(), true, None);
        Ok(())
    }

    async fn export_dashboard_excel(
        &self,
        config: &DashboardConfig,
        options: &ExportOptions,
        export_id: &str,
    ) -> Result<(), String> {
        // This would require a WASM-compatible Excel library
        // For now, export as CSV with Excel-friendly formatting
        self.export_dashboard_csv(config, options, export_id).await
    }

    async fn export_canvas_as_image(
        &self,
        canvas: &HtmlCanvasElement,
        options: &ExportOptions,
    ) -> Result<(), String> {
        let format_str = match options.format {
            ExportFormat::Png => "image/png",
            ExportFormat::Jpeg => "image/jpeg",
            _ => return Err("Unsupported image format".to_string()),
        };

        let quality = options.quality.unwrap_or(0.95);
        let data_url = canvas.to_data_url_with_type_and_encoder_options(format_str, &JsValue::from_f64(quality))
            .map_err(|_| "Failed to generate image data")?;

        let filename = generate_filename(options, "chart");
        self.download_data_url(&data_url, &filename)?;

        Ok(())
    }

    async fn export_canvas_as_pdf(
        &self,
        canvas: &HtmlCanvasElement,
        title: &str,
        options: &ExportOptions,
    ) -> Result<(), String> {
        // Convert canvas to image first, then embed in PDF
        let data_url = canvas.to_data_url()
            .map_err(|_| "Failed to generate image data")?;

        // This would require jsPDF or similar library
        // For now, just download as PNG
        let filename = generate_filename(options, title);
        self.download_data_url(&data_url, &filename.replace(".pdf", ".png"))?;

        Ok(())
    }

    fn create_dashboard_canvas(
        &self,
        config: &DashboardConfig,
        options: &ExportOptions,
    ) -> Result<HtmlCanvasElement, String> {
        let document = document().ok_or("No document available")?;
        let canvas = document
            .create_element("canvas")
            .map_err(|_| "Failed to create canvas")?
            .dyn_into::<HtmlCanvasElement>()
            .map_err(|_| "Failed to cast to canvas")?;

        let width = options.width.unwrap_or(1920);
        let height = options.height.unwrap_or(1080);

        canvas.set_width(width);
        canvas.set_height(height);

        let context = canvas
            .get_context("2d")
            .map_err(|_| "Failed to get canvas context")?
            .ok_or("No canvas context")?
            .dyn_into::<web_sys::CanvasRenderingContext2d>()
            .map_err(|_| "Failed to cast to 2d context")?;

        // Fill background
        if let Some(bg_color) = &options.background_color {
            context.set_fill_style(&JsValue::from_str(bg_color));
            context.fill_rect(0.0, 0.0, width as f64, height as f64);
        }

        // Render dashboard content
        self.render_dashboard_to_canvas(&context, config, width, height)?;

        Ok(canvas)
    }

    fn render_dashboard_to_canvas(
        &self,
        context: &web_sys::CanvasRenderingContext2d,
        config: &DashboardConfig,
        width: u32,
        height: u32,
    ) -> Result<(), String> {
        // Calculate grid dimensions
        let columns = config.layout.columns as f64;
        let column_width = width as f64 / columns;
        let row_height = config.layout.row_height as f64;

        // Draw grid background
        context.set_stroke_style(&JsValue::from_str("#e0e0e0"));
        context.set_line_width(1.0);

        // Vertical lines
        for i in 0..=config.layout.columns {
            let x = i as f64 * column_width;
            context.begin_path();
            context.move_to(x, 0.0);
            context.line_to(x, height as f64);
            context.stroke();
        }

        // Horizontal lines
        let rows = (height as f64 / row_height).ceil() as i32;
        for i in 0..=rows {
            let y = i as f64 * row_height;
            context.begin_path();
            context.move_to(0.0, y);
            context.line_to(width as f64, y);
            context.stroke();
        }

        // Render widgets
        context.set_fill_style(&JsValue::from_str("#333333"));
        context.set_font("14px Arial");

        for (id, widget) in &config.widgets {
            let x = widget.position.x as f64 * column_width;
            let y = widget.position.y as f64 * row_height;
            let w = widget.size.width as f64 * column_width;
            let h = widget.size.height as f64 * row_height;

            // Draw widget border
            context.set_stroke_style(&JsValue::from_str("#007bff"));
            context.set_line_width(2.0);
            context.stroke_rect(x, y, w, h);

            // Draw widget title
            context.set_fill_style(&JsValue::from_str("#333333"));
            let _ = context.fill_text(&widget.title, x + 10.0, y + 25.0);

            // Draw widget type
            context.set_fill_style(&JsValue::from_str("#666666"));
            let _ = context.fill_text(&format!("Type: {:?}", widget.widget_type), x + 10.0, y + 45.0);
        }

        Ok(())
    }

    fn generate_dashboard_svg(
        &self,
        config: &DashboardConfig,
        options: &ExportOptions,
    ) -> Result<String, String> {
        let width = options.width.unwrap_or(1920);
        let height = options.height.unwrap_or(1080);

        let mut svg = format!(
            r#"<svg width="{}" height="{}" xmlns="http://www.w3.org/2000/svg">"#,
            width, height
        );

        // Background
        if let Some(bg_color) = &options.background_color {
            svg.push_str(&format!(
                r#"<rect width="100%" height="100%" fill="{}"/>"#,
                bg_color
            ));
        }

        // Grid
        let columns = config.layout.columns as f64;
        let column_width = width as f64 / columns;
        let row_height = config.layout.row_height as f64;

        // Render widgets as SVG rectangles
        for (_, widget) in &config.widgets {
            let x = widget.position.x as f64 * column_width;
            let y = widget.position.y as f64 * row_height;
            let w = widget.size.width as f64 * column_width;
            let h = widget.size.height as f64 * row_height;

            svg.push_str(&format!(
                r#"<rect x="{}" y="{}" width="{}" height="{}" fill="none" stroke="#007bff" stroke-width="2"/>"#,
                x, y, w, h
            ));

            svg.push_str(&format!(
                r#"<text x="{}" y="{}" font-family="Arial" font-size="14" fill="#333">{}</text>"#,
                x + 10.0, y + 25.0, escape_xml(&widget.title)
            ));
        }

        svg.push_str("</svg>");
        Ok(svg)
    }

    fn download_text_file(&self, content: &str, filename: &str, mime_type: &str) -> Result<(), String> {
        let window = window().ok_or("No window available")?;
        let document = window.document().ok_or("No document available")?;

        // Create blob
        let array = js_sys::Array::new();
        array.push(&JsValue::from_str(content));

        let blob_parts = js_sys::Object::new();
        js_sys::Reflect::set(&blob_parts, &"type".into(), &mime_type.into()).unwrap();

        let blob = Blob::new_with_str_sequence_and_options(&array, &blob_parts)
            .map_err(|_| "Failed to create blob")?;

        // Create download link
        let url = Url::create_object_url_with_blob(&blob)
            .map_err(|_| "Failed to create object URL")?;

        let anchor = document
            .create_element("a")
            .map_err(|_| "Failed to create anchor element")?
            .dyn_into::<HtmlAnchorElement>()
            .map_err(|_| "Failed to cast to anchor")?;

        anchor.set_href(&url);
        anchor.set_download(filename);
        anchor.click();

        // Cleanup
        Url::revoke_object_url(&url).unwrap();

        Ok(())
    }

    fn download_data_url(&self, data_url: &str, filename: &str) -> Result<(), String> {
        let window = window().ok_or("No window available")?;
        let document = window.document().ok_or("No document available")?;

        let anchor = document
            .create_element("a")
            .map_err(|_| "Failed to create anchor element")?
            .dyn_into::<HtmlAnchorElement>()
            .map_err(|_| "Failed to cast to anchor")?;

        anchor.set_href(data_url);
        anchor.set_download(filename);
        anchor.click();

        Ok(())
    }

    fn record_export(
        &self,
        id: &str,
        filename: &str,
        format: ExportFormat,
        size_bytes: usize,
        success: bool,
        error_message: Option<String>,
    ) {
        let record = ExportRecord {
            id: id.to_string(),
            filename: filename.to_string(),
            format,
            size_bytes,
            exported_at: Utc::now(),
            success,
            error_message,
        };

        self.export_history.update(|history| {
            history.push(record);
            // Keep only last 100 exports
            if history.len() > 100 {
                history.remove(0);
            }
        });
    }

    pub fn get_export_history(&self) -> ReadSignal<Vec<ExportRecord>> {
        self.export_history.into()
    }

    pub fn clear_export_history(&self) {
        self.export_history.set(Vec::new());
    }
}

// Utility functions
fn generate_filename(options: &ExportOptions, base_name: &str) -> String {
    if let Some(filename) = &options.filename {
        if filename.contains('.') {
            filename.clone()
        } else {
            format!("{}.{}", filename, options.format.extension())
        }
    } else {
        let timestamp = Utc::now().format("%Y%m%d_%H%M%S");
        format!("{}_{}.{}", base_name, timestamp, options.format.extension())
    }
}

fn escape_csv_field(field: &str) -> String {
    if field.contains(',') || field.contains('"') || field.contains('\n') {
        format!("\"{}\"", field.replace("\"", "\"\""))
    } else {
        field.to_string()
    }
}

fn escape_xml(text: &str) -> String {
    text.replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
        .replace('"', "&quot;")
        .replace('\'', "&apos;")
}

// Export widget component for easy integration
#[component]
pub fn ExportWidget(
    #[prop(optional)] on_export: Option<Box<dyn Fn(ExportFormat) + 'static + Send + Sync>>,
    #[prop(optional)] compact: Option<bool>,
) -> impl IntoView {
    let (show_menu, set_show_menu) = signal(false);
    let compact = compact.unwrap_or(false);

    let export_formats = vec![
        ExportFormat::Png,
        ExportFormat::Pdf,
        ExportFormat::Csv,
        ExportFormat::Json,
    ];

    let handle_export = move |format: ExportFormat| {
        if let Some(handler) = &on_export {
            handler(format);
        }
        set_show_menu.set(false);
    };

    view! {
        <div class="export-widget">
            <button
                class="export-trigger btn-icon"
                on:click=move |_| set_show_menu.update(|show| *show = !*show)
                title="Export"
            >
                <i class="bi-download"></i>
                <Show when=move || !compact>
                    <span class="btn-text">"Export"</span>
                </Show>
            </button>

            <Show when=move || show_menu.get()>
                <div class="export-menu">
                    <div class="export-menu-header">
                        <h4>"Export Options"</h4>
                        <button
                            class="btn-icon close-btn"
                            on:click=move |_| set_show_menu.set(false)
                        >
                            <i class="bi-x"></i>
                        </button>
                    </div>

                    <div class="export-options">
                        <For
                            each=move || export_formats.clone()
                            key=|format| format!("{:?}", format)
                            children=move |format| {
                                let format_clone = format.clone();
                                view! {
                                    <button
                                        class="export-option"
                                        on:click=move |_| handle_export(format_clone.clone())
                                    >
                                        <i class=format_icon(&format)></i>
                                        <div class="option-details">
                                            <span class="option-name">{format.display_name()}</span>
                                            <small class="option-desc">{format_description(&format)}</small>
                                        </div>
                                    </button>
                                }
                            }
                        />
                    </div>
                </div>
            </Show>
        </div>
    }
}

fn format_icon(format: &ExportFormat) -> &'static str {
    match format {
        ExportFormat::Png | ExportFormat::Jpeg => "bi-image",
        ExportFormat::Pdf => "bi-file-earmark-pdf",
        ExportFormat::Svg => "bi-vector-pen",
        ExportFormat::Csv => "bi-filetype-csv",
        ExportFormat::Excel => "bi-file-earmark-spreadsheet",
        ExportFormat::Json => "bi-file-earmark-code",
    }
}

fn format_description(format: &ExportFormat) -> &'static str {
    match format {
        ExportFormat::Png => "High-quality raster image",
        ExportFormat::Jpeg => "Compressed raster image",
        ExportFormat::Pdf => "Portable document format",
        ExportFormat::Svg => "Scalable vector graphics",
        ExportFormat::Csv => "Comma-separated values",
        ExportFormat::Excel => "Microsoft Excel format",
        ExportFormat::Json => "Structured data format",
    }
}
