//! Portfolio tracker with multi-asset P&L view.

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

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

use termichart_data::ExchangeClient;
use crate::sparkline;

/// A single holding in the portfolio.
#[derive(Debug, Clone)]
pub struct Holding {
    pub symbol: String,
    pub quantity: f64,
    pub entry_price: f64,
}

impl Holding {
    pub fn new(symbol: impl Into<String>, quantity: f64, entry_price: f64) -> Self {
        Self {
            symbol: symbol.into(),
            quantity,
            entry_price,
        }
    }
}

/// Portfolio tracker that monitors multiple holdings with live prices.
pub struct Portfolio {
    holdings: Vec<Holding>,
    price_history: Vec<Vec<f64>>,
    width: u16,
    height: u16,
}

impl Portfolio {
    pub fn new(holdings: Vec<Holding>) -> Self {
        let n = holdings.len();
        Self {
            holdings,
            price_history: vec![Vec::new(); n],
            width: 80,
            height: 24,
        }
    }

    pub fn size(mut self, width: u16, height: u16) -> Self {
        self.width = width;
        self.height = height;
        self
    }

    /// Run the portfolio tracker in the terminal.
    pub fn run(&mut self) -> std::result::Result<(), Box<dyn std::error::Error>> {
        let mut stdout = std::io::stdout();
        let total_lines = self.height + 1;

        let client = ExchangeClient::binance();

        // Reserve space
        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 = Duration::from_secs(5);

        loop {
            if event::poll(poll)? {
                if let Event::Key(KeyEvent { code, .. }) = event::read()? {
                    match code {
                        KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => break,
                        KeyCode::Char('c') => break,
                        _ => {}
                    }
                }
            }

            // Fetch current prices
            let mut current_prices: Vec<Option<f64>> = Vec::new();
            for (i, holding) in self.holdings.iter().enumerate() {
                let price = client.fetch_spot_price(&holding.symbol);
                if let Some(p) = price {
                    if self.price_history[i].len() >= 20 {
                        self.price_history[i].remove(0);
                    }
                    self.price_history[i].push(p);
                }
                current_prices.push(price);
            }

            // Build output
            let mut lines: Vec<String> = Vec::new();

            // Header
            lines.push(format!(
                "\x1b[1;37m{:<12} {:>10} {:>12} {:>12} {:>10} {:>10} {}\x1b[0m",
                "SYMBOL", "QTY", "ENTRY", "CURRENT", "P&L", "P&L %", "TREND"
            ));
            lines.push(format!("{}", "\u{2500}".repeat(self.width as usize)));

            let mut total_cost = 0.0_f64;
            let mut total_value = 0.0_f64;

            for (i, holding) in self.holdings.iter().enumerate() {
                let current = current_prices[i].unwrap_or(holding.entry_price);
                let cost = holding.quantity * holding.entry_price;
                let value = holding.quantity * current;
                let pnl = value - cost;
                let pnl_pct = if cost > 0.0 { (pnl / cost) * 100.0 } else { 0.0 };

                total_cost += cost;
                total_value += value;

                let pnl_color = if pnl >= 0.0 { "\x1b[32m" } else { "\x1b[31m" };

                // Format symbol nicely
                let sym = format_portfolio_symbol(&holding.symbol);

                // Sparkline from price history
                let spark = if self.price_history[i].len() >= 2 {
                    sparkline::sparkline(&self.price_history[i])
                } else {
                    String::from("...")
                };

                lines.push(format!(
                    "{:<12} {:>10.4} {:>12.2} {:>12.2} {}{:>9.2}{} {}{:>9.2}%{} {}",
                    sym,
                    holding.quantity,
                    holding.entry_price,
                    current,
                    pnl_color, pnl, "\x1b[0m",
                    pnl_color, pnl_pct, "\x1b[0m",
                    spark,
                ));
            }

            // Separator + totals
            lines.push(format!("{}", "\u{2500}".repeat(self.width as usize)));

            let total_pnl = total_value - total_cost;
            let total_pnl_pct = if total_cost > 0.0 { (total_pnl / total_cost) * 100.0 } else { 0.0 };
            let total_color = if total_pnl >= 0.0 { "\x1b[1;32m" } else { "\x1b[1;31m" };

            lines.push(format!(
                "{:<12} {:>10} {:>12.2} {:>12.2} {}{:>9.2}{} {}{:>9.2}%{}",
                "TOTAL", "",
                total_cost, total_value,
                total_color, total_pnl, "\x1b[0m",
                total_color, total_pnl_pct, "\x1b[0m",
            ));

            // Pad remaining lines
            while lines.len() < self.height as usize {
                lines.push(String::new());
            }

            let output = lines.join("\r\n");

            queue!(stdout, BeginSynchronizedUpdate, cursor::MoveTo(0, anchor_row))?;
            stdout.write_all(output.as_bytes())?;

            write!(
                stdout,
                "\r\n\x1b[7m Portfolio \x1b[0m  {} assets  |  Total: ${:.2}  |  \x1b[2m'q' quit\x1b[0m\x1b[K",
                self.holdings.len(), total_value,
            )?;

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

        execute!(stdout, cursor::MoveTo(0, anchor_row + total_lines), cursor::Show)?;
        terminal::disable_raw_mode()?;
        println!("Portfolio session ended.");

        Ok(())
    }
}

fn format_portfolio_symbol(sym: &str) -> String {
    let s = sym.to_uppercase();
    for base in &["USDT", "BUSD", "USD"] {
        if s.ends_with(base) && s.len() > base.len() {
            let prefix = &s[..s.len() - base.len()];
            return format!("{}/{}", prefix, base);
        }
    }
    s
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn holding_creation() {
        let h = Holding::new("BTCUSDT", 0.5, 50000.0);
        assert_eq!(h.symbol, "BTCUSDT");
        assert!((h.quantity - 0.5).abs() < f64::EPSILON);
        assert!((h.entry_price - 50000.0).abs() < f64::EPSILON);
    }

    #[test]
    fn portfolio_creation() {
        let holdings = vec![
            Holding::new("BTCUSDT", 0.5, 50000.0),
            Holding::new("ETHUSDT", 10.0, 3000.0),
        ];
        let portfolio = Portfolio::new(holdings);
        assert_eq!(portfolio.holdings.len(), 2);
    }

    #[test]
    fn format_symbol() {
        assert_eq!(format_portfolio_symbol("BTCUSDT"), "BTC/USDT");
        assert_eq!(format_portfolio_symbol("ETHUSDT"), "ETH/USDT");
        assert_eq!(format_portfolio_symbol("UNKNOWN"), "UNKNOWN");
    }
}
