//! Price alert / watch mode -- monitors a symbol with configurable alerts.
//!
//! Displays a live candlestick chart (with Heikin-Ashi smoothing) and a
//! status bar that shows the current price, alert thresholds, and alert
//! state.  When the price crosses an alert threshold the terminal bell
//! rings and the status bar flashes with a coloured alert message.
//!
//! Uses the same inline rendering pattern as [`super::interactive`] and the
//! `realtime_btc` example:
//! - Reserve vertical space, query cursor position for `anchor_row`.
//! - `MoveTo(0, anchor_row)` each frame.
//! - `'\n'` replaced with `'\r\n'` for raw mode.
//! - Each frame wrapped in `BeginSynchronizedUpdate` / `EndSynchronizedUpdate`.

use std::io::Write;
use std::time::Duration;

use crossterm::{
    cursor,
    event::{self, Event, KeyCode, KeyEventKind},
    execute, queue,
    terminal::{self, BeginSynchronizedUpdate, EndSynchronizedUpdate},
};

use termichart_core::OverlayKind;
use termichart_data::{Exchange, ExchangeClient};

use crate::ChartBuilder;

// ---------------------------------------------------------------------------
// PriceAlert
// ---------------------------------------------------------------------------

/// Configuration for price alert thresholds.
#[derive(Debug, Clone)]
pub struct PriceAlert {
    /// Trigger when the price rises **above** this value.
    pub above: Option<f64>,
    /// Trigger when the price falls **below** this value.
    pub below: Option<f64>,
}

impl Default for PriceAlert {
    fn default() -> Self {
        Self {
            above: None,
            below: None,
        }
    }
}

// ---------------------------------------------------------------------------
// WatchMode
// ---------------------------------------------------------------------------

/// Watch mode -- monitors a symbol with price alerts and a live chart.
///
/// # Example
///
/// ```no_run
/// use termichart_charts::WatchMode;
/// use termichart_data::Exchange;
///
/// WatchMode::new("BTCUSDT".into())
///     .exchange(Exchange::Binance)
///     .alert_above(100_000.0)
///     .alert_below(90_000.0)
///     .run()
///     .unwrap();
/// ```
pub struct WatchMode {
    symbol: String,
    exchange: Exchange,
    alert: PriceAlert,
    width: u16,
    height: u16,
    /// Interval string passed to `ExchangeClient::fetch_candles`.
    interval: String,
    /// Whether the above-alert has already been triggered (resets when
    /// price drops back within range so it can fire again).
    triggered_above: bool,
    /// Whether the below-alert has already been triggered.
    triggered_below: bool,
}

impl WatchMode {
    /// Create a new watch session for `symbol`.
    ///
    /// Defaults to Binance, 80x24, 1-minute candles, no alerts.
    pub fn new(symbol: String) -> Self {
        Self {
            symbol,
            exchange: Exchange::Binance,
            alert: PriceAlert::default(),
            width: 80,
            height: 24,
            interval: "1m".into(),
            triggered_above: false,
            triggered_below: false,
        }
    }

    /// Set the alert-above threshold.
    pub fn alert_above(mut self, price: f64) -> Self {
        self.alert.above = Some(price);
        self
    }

    /// Set the alert-below threshold.
    pub fn alert_below(mut self, price: f64) -> Self {
        self.alert.below = Some(price);
        self
    }

    /// Set the exchange to use (default: Binance).
    pub fn exchange(mut self, exchange: Exchange) -> Self {
        self.exchange = exchange;
        self
    }

    /// Set the chart / fetch dimensions (default: 80x24).
    pub fn size(mut self, width: u16, height: u16) -> Self {
        self.width = width;
        self.height = height;
        self
    }

    /// Set the candle interval string (default: `"1m"`).
    ///
    /// For Binance this is e.g. `"1m"`, `"5m"`, `"1h"`.
    /// For CoinGecko this is interpreted as the number of days.
    pub fn interval(mut self, interval: impl Into<String>) -> Self {
        self.interval = interval.into();
        self
    }

    // -- alert logic -------------------------------------------------------

    /// Check alerts against the current price.  Returns an optional alert
    /// message string (with ANSI colours) and whether the bell should ring.
    fn check_alerts(&mut self, price: f64) -> (Option<String>, bool) {
        let mut msg: Option<String> = None;
        let mut bell = false;

        // -- above alert ---------------------------------------------------
        if let Some(threshold) = self.alert.above {
            if price > threshold {
                // Bright green text, bold.
                msg = Some(format!(
                    "\x1b[1;92mALERT! Above ${:.2}\x1b[0m",
                    threshold
                ));
                if !self.triggered_above {
                    bell = true;
                    self.triggered_above = true;
                }
            } else {
                // Price went back within range -- reset so it can trigger again.
                self.triggered_above = false;
            }
        }

        // -- below alert ---------------------------------------------------
        if let Some(threshold) = self.alert.below {
            if price < threshold {
                // Bright red text, bold.  Overrides any above-alert message
                // (if somehow both trigger, below is more urgent).
                msg = Some(format!(
                    "\x1b[1;91mALERT! Below ${:.2}\x1b[0m",
                    threshold
                ));
                if !self.triggered_below {
                    bell = true;
                    self.triggered_below = true;
                }
            } else {
                self.triggered_below = false;
            }
        }

        (msg, bell)
    }

    /// Build the status bar string.
    fn status_bar(&self, price: f64, alert_msg: &Option<String>) -> String {
        // Current price in inverted colours.
        let mut bar = format!("\x1b[7m ${:.2} \x1b[0m", price);

        // Alert thresholds (dimmed).
        if let Some(above) = self.alert.above {
            bar.push_str(&format!("  \x1b[2mabove ${:.2}\x1b[0m", above));
        }
        if let Some(below) = self.alert.below {
            bar.push_str(&format!("  \x1b[2mbelow ${:.2}\x1b[0m", below));
        }

        // Alert message (bright coloured).
        if let Some(ref alert) = alert_msg {
            bar.push_str("  ");
            bar.push_str(alert);
        }

        // Quit hint (dimmed).
        bar.push_str("  \x1b[2m'q' quit\x1b[0m");

        // Erase to end of line so leftover characters from previous
        // longer status bars are cleared.
        bar.push_str("\x1b[K");

        bar
    }

    /// Run the watch loop.
    ///
    /// Shows a live Heikin-Ashi candlestick chart with SMA(7) overlay and a
    /// status bar.  Fetches new data every 5 seconds.  Press `q` or `Esc`
    /// to exit.
    pub fn run(&mut self) -> Result<(), Box<dyn std::error::Error>> {
        let mut stdout = std::io::stdout();
        let client = ExchangeClient::new(self.exchange);

        // chart_height = total height minus 1 row for the status bar.
        let chart_height = self.height.saturating_sub(1);
        let total_lines = self.height + 1;

        // How many candles fit (3-col body + 1-col gap, minus ~10 for y-axis).
        let chart_cols = (self.width as usize).saturating_sub(10);
        let num_candles = (chart_cols / 4).max(10);

        // Probe connectivity.
        eprint!("Connecting to {:?}...", self.exchange);
        if client
            .fetch_candles(&self.symbol, &self.interval, 2)
            .is_none()
        {
            eprintln!(" failed! Check your internet connection or symbol.");
            return Ok(());
        }
        eprintln!(" connected.  Watching {}.", self.symbol);

        // Reserve vertical space so inline rendering does not scroll off.
        for _ in 0..total_lines {
            writeln!(stdout)?;
        }
        stdout.flush()?;

        terminal::enable_raw_mode()?;
        execute!(stdout, cursor::Hide)?;

        let (_, cursor_row) = cursor::position()?;
        let anchor_row = cursor_row.saturating_sub(total_lines);

        let poll_timeout = Duration::from_secs(5);

        loop {
            // Poll for keyboard events (also acts as the refresh timer).
            if event::poll(poll_timeout)? {
                if let Event::Key(key) = event::read()? {
                    if key.kind != KeyEventKind::Press {
                        continue;
                    }
                    match key.code {
                        KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => break,
                        _ => {}
                    }
                }
            }

            // Fetch candles.
            let candles = match client.fetch_candles(&self.symbol, &self.interval, num_candles) {
                Some(c) if !c.is_empty() => c,
                _ => continue,
            };

            // Determine current price from the last candle's close.
            let last = candles.last().unwrap();
            let current_price = last.close;

            // Check price alerts.
            let (alert_msg, ring_bell) = self.check_alerts(current_price);

            // Build the chart title.
            let bull = last.close >= last.open;
            let arrow = if bull { "\u{25B2}" } else { "\u{25BC}" };
            let title = format!(
                "{} {}  {}  ${:.2}  |  {} candles",
                self.symbol,
                self.interval,
                arrow,
                current_price,
                candles.len(),
            );

            // Render the chart.
            let output = match ChartBuilder::candlestick()
                .size(self.width, chart_height)
                .title(title)
                .x_axis_time()
                .heikin_ashi()
                .overlay(OverlayKind::Sma(7))
                .candles(candles)
                .render()
            {
                Ok(s) => s,
                Err(_) => continue,
            };

            // Convert newlines for raw mode.
            let output = output.replace('\n', "\r\n");

            // Build the status bar.
            let status = self.status_bar(current_price, &alert_msg);

            // Draw frame.
            queue!(
                stdout,
                BeginSynchronizedUpdate,
                cursor::MoveTo(0, anchor_row)
            )?;
            stdout.write_all(output.as_bytes())?;
            write!(stdout, "\r{}", status)?;

            // Ring the bell if an alert just triggered.
            if ring_bell {
                write!(stdout, "\x07")?;
            }

            queue!(stdout, EndSynchronizedUpdate)?;
            stdout.flush()?;
        }

        // Cleanup: restore terminal state.
        execute!(
            stdout,
            cursor::MoveTo(0, anchor_row + total_lines),
            cursor::Show
        )?;
        terminal::disable_raw_mode()?;
        println!("Watch session ended.");

        Ok(())
    }
}
