use pyo3::prelude::*;
use termichart_core::{Candle, ChartConfig, ChartType, Point, Style, Viewport};
use termichart_data::JsonAdapter;
use termichart_core::DataAdapter;
use termichart_render::{CellGrid, BrailleCanvas, BlockCanvas, AxisRenderer, Layout, render_to_string, painter};

/// A Python-accessible point.
#[pyclass]
#[derive(Clone)]
struct PyPoint {
    #[pyo3(get, set)]
    x: f64,
    #[pyo3(get, set)]
    y: f64,
}

#[pymethods]
impl PyPoint {
    #[new]
    fn new(x: f64, y: f64) -> Self {
        PyPoint { x, y }
    }

    fn __repr__(&self) -> String {
        format!("Point(x={}, y={})", self.x, self.y)
    }
}

/// A Python-accessible OHLCV candle.
#[pyclass]
#[derive(Clone)]
struct PyCandle {
    #[pyo3(get, set)]
    time: f64,
    #[pyo3(get, set)]
    open: f64,
    #[pyo3(get, set)]
    high: f64,
    #[pyo3(get, set)]
    low: f64,
    #[pyo3(get, set)]
    close: f64,
    #[pyo3(get, set)]
    volume: f64,
}

#[pymethods]
impl PyCandle {
    #[new]
    fn new(time: f64, open: f64, high: f64, low: f64, close: f64, volume: f64) -> Self {
        PyCandle { time, open, high, low, close, volume }
    }

    fn __repr__(&self) -> String {
        format!(
            "Candle(time={}, O={}, H={}, L={}, C={}, V={})",
            self.time, self.open, self.high, self.low, self.close, self.volume
        )
    }
}

/// A fluent chart builder for Python.
#[pyclass]
struct PyChart {
    chart_type: String,
    points: Vec<Point>,
    candles: Vec<Candle>,
    width: Option<u16>,
    height: Option<u16>,
    title: Option<String>,
}

#[pymethods]
impl PyChart {
    #[new]
    #[pyo3(signature = (chart_type="line"))]
    fn new(chart_type: &str) -> Self {
        PyChart {
            chart_type: chart_type.to_string(),
            points: Vec::new(),
            candles: Vec::new(),
            width: None,
            height: None,
            title: None,
        }
    }

    /// Add a point and return self for chaining.
    fn add_point(&mut self, x: f64, y: f64) -> PyResult<()> {
        self.points.push(Point { x, y });
        Ok(())
    }

    /// Add a candle and return self for chaining.
    fn add_candle(
        &mut self,
        time: f64,
        open: f64,
        high: f64,
        low: f64,
        close: f64,
        volume: f64,
    ) -> PyResult<()> {
        self.candles.push(Candle { time, open, high, low, close, volume });
        Ok(())
    }

    /// Set the chart size.
    fn size(&mut self, width: u16, height: u16) -> PyResult<()> {
        self.width = Some(width);
        self.height = Some(height);
        Ok(())
    }

    /// Set the chart title.
    fn set_title(&mut self, title: &str) -> PyResult<()> {
        self.title = Some(title.to_string());
        Ok(())
    }

    /// Load data from a JSON string.
    fn load_json(&mut self, json_str: &str) -> PyResult<()> {
        let adapter = JsonAdapter::from_str(json_str);
        if self.chart_type == "candlestick" {
            let candles = adapter.candles().map_err(|e| {
                pyo3::exceptions::PyValueError::new_err(format!("{}", e))
            })?;
            self.candles = candles;
        } else {
            let points = adapter.points().map_err(|e| {
                pyo3::exceptions::PyValueError::new_err(format!("{}", e))
            })?;
            self.points = points;
        }
        Ok(())
    }

    /// Render the chart to an ANSI string.
    fn render(&self) -> PyResult<String> {
        let w = self.width.unwrap_or(80);
        let h = self.height.unwrap_or(24);

        let config = ChartConfig {
            chart_type: match self.chart_type.as_str() {
                "candlestick" => ChartType::Candlestick,
                "bar" => ChartType::Bar,
                "histogram" => ChartType::Histogram,
                _ => ChartType::Line,
            },
            width: Some(w),
            height: Some(h),
            title: self.title.clone(),
            ..Default::default()
        };

        let layout = Layout::compute(w, h, &config);
        let mut grid = CellGrid::new(w, h);
        let chart_area = layout.chart_area;

        match config.chart_type {
            ChartType::Line => {
                if self.points.is_empty() {
                    return Err(pyo3::exceptions::PyValueError::new_err("No points to render"));
                }
                let (x_range, y_range) = termichart_data::auto_range_points(&self.points);
                let viewport = Viewport {
                    x_range,
                    y_range,
                    width: chart_area.width,
                    height: chart_area.height,
                };

                let mut canvas = BrailleCanvas::new(chart_area.width, chart_area.height);
                for i in 1..self.points.len() {
                    let x0 = viewport.map_x(self.points[i - 1].x);
                    let y0 = viewport.map_y(self.points[i - 1].y);
                    let x1 = viewport.map_x(self.points[i].x);
                    let y1 = viewport.map_y(self.points[i].y);
                    canvas.draw_line(
                        (x0 * 2.0) as i32,
                        (y0 * 4.0) as i32,
                        (x1 * 2.0) as i32,
                        (y1 * 4.0) as i32,
                    );
                }
                let style = Style {
                    fg: config.theme.bull_color(),
                    ..Default::default()
                };
                canvas.render_to_grid(&mut grid, chart_area.x, chart_area.y, style);
            }
            ChartType::Candlestick => {
                if self.candles.is_empty() {
                    return Err(pyo3::exceptions::PyValueError::new_err("No candles to render"));
                }
                let (x_range, y_range) = termichart_data::auto_range(&self.candles);
                let viewport = Viewport {
                    x_range,
                    y_range,
                    width: chart_area.width,
                    height: chart_area.height,
                };

                let block = BlockCanvas::new(chart_area.width, chart_area.height);
                for candle in &self.candles {
                    let col = viewport.map_col(candle.time) + chart_area.x;
                    let is_bull = candle.close >= candle.open;
                    let color = if is_bull {
                        config.theme.bull_color()
                    } else {
                        config.theme.bear_color()
                    };
                    let style = Style { fg: color, ..Default::default() };

                    let wick_top = viewport.map_row(candle.high) + chart_area.y;
                    let wick_bottom = viewport.map_row(candle.low) + chart_area.y;
                    block.draw_wick(&mut grid, col, wick_top, wick_bottom, style);

                    let body_top = viewport.map_row(if is_bull { candle.close } else { candle.open }) + chart_area.y;
                    let body_bottom = viewport.map_row(if is_bull { candle.open } else { candle.close }) + chart_area.y;
                    block.draw_candle_body(&mut grid, col, body_top, body_bottom, style);
                }
            }
            ChartType::Bar => {
                if self.points.is_empty() {
                    return Err(pyo3::exceptions::PyValueError::new_err("No points to render"));
                }
                let (x_range, y_range) = termichart_data::auto_range_points(&self.points);
                let viewport = Viewport {
                    x_range,
                    y_range,
                    width: chart_area.width,
                    height: chart_area.height,
                };
                let bar_chart = termichart_charts::BarChart::new(self.points.clone());
                bar_chart.render(&mut grid, &viewport, &config, chart_area.x, chart_area.y)
                    .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{}", e)))?;
            }
            ChartType::Histogram => {
                // For histogram, extract y values from points as raw data
                if self.points.is_empty() {
                    return Err(pyo3::exceptions::PyValueError::new_err("No values to render"));
                }
                let values: Vec<f64> = self.points.iter().map(|p| p.y).collect();
                let histogram = termichart_charts::Histogram::new(values, 10);
                let bins = histogram.compute_bins();
                let (x_range, y_range) = if bins.is_empty() {
                    (termichart_core::Range { min: 0.0, max: 1.0 }, termichart_core::Range { min: 0.0, max: 1.0 })
                } else {
                    let x_min = bins.first().map(|(lo, _, _)| *lo).unwrap_or(0.0);
                    let x_max = bins.last().map(|(_, hi, _)| *hi).unwrap_or(1.0);
                    let max_count = bins.iter().map(|(_, _, c)| *c).max().unwrap_or(1);
                    (
                        termichart_core::Range { min: x_min, max: x_max },
                        termichart_core::Range { min: 0.0, max: max_count as f64 },
                    )
                };
                let viewport = Viewport {
                    x_range,
                    y_range,
                    width: chart_area.width,
                    height: chart_area.height,
                };
                histogram.render(&mut grid, &viewport, &config, chart_area.x, chart_area.y)
                    .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{}", e)))?;
            }
        }

        if config.show_axis {
            let (x_range, y_range) = match config.chart_type {
                ChartType::Line | ChartType::Bar | ChartType::Histogram => {
                    if !self.points.is_empty() {
                        termichart_data::auto_range_points(&self.points)
                    } else {
                        (termichart_core::Range { min: 0.0, max: 1.0 }, termichart_core::Range { min: 0.0, max: 1.0 })
                    }
                }
                ChartType::Candlestick => {
                    termichart_data::auto_range(&self.candles)
                }
            };
            let viewport = Viewport {
                x_range,
                y_range,
                width: chart_area.width,
                height: chart_area.height,
            };
            let axis_renderer = AxisRenderer;
            axis_renderer.draw_y_axis(&mut grid, &viewport, &layout.y_axis_area, &config.theme);
            axis_renderer.draw_x_axis(&mut grid, &viewport, &layout.x_axis_area, &config.theme, config.x_axis_format);
        }

        if let (Some(title), Some(title_area)) = (&config.title, &layout.title_area) {
            let style = Style {
                fg: config.theme.text_color(),
                bold: true,
                ..Default::default()
            };
            painter::draw_text(&mut grid, title_area.x, title_area.y, title, style);
        }

        Ok(render_to_string(&grid))
    }

    /// Render the chart and print to stdout.
    fn show(&self) -> PyResult<()> {
        let output = self.render()?;
        print!("{}", output);
        Ok(())
    }
}

/// Load chart data from a JSON file.
#[pyfunction]
fn load_json(path: &str) -> PyResult<String> {
    std::fs::read_to_string(path).map_err(|e| {
        pyo3::exceptions::PyIOError::new_err(format!("{}", e))
    })
}

/// The termichart Python module.
#[pymodule]
fn termichart(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_class::<PyPoint>()?;
    m.add_class::<PyCandle>()?;
    m.add_class::<PyChart>()?;
    m.add_function(wrap_pyfunction!(load_json, m)?)?;
    Ok(())
}
