use std::cmp::max;
use std::sync::Arc;
use std::time::Duration;
use std::time::SystemTime;

use crossterm::event::Event;
use crossterm::event::KeyCode;
use crossterm::event::KeyEventKind;
use neptune_cash::api::tx_initiation::builder::tx_output_list_builder::OutputFormat;
use neptune_cash::application::config::network::Network;
use neptune_cash::application::rpc::auth;
use neptune_cash::protocol::consensus::type_scripts::native_currency_amount::NativeCurrencyAmount;
use neptune_cash::state::wallet::address::KeyType;
use neptune_cash::state::wallet::address::ReceivingAddress;
use neptune_cash::state::wallet::change_policy::ChangePolicy;
use neptune_cash::state::wallet::utxo_notification::UtxoNotificationMedium;
use ratatui::layout::Alignment;
use ratatui::layout::Margin;
use ratatui::style::Color;
use ratatui::style::Modifier;
use ratatui::style::Style;
use ratatui::text::Line;
use ratatui::text::Span;
use ratatui::text::Text;
use ratatui::widgets::Block;
use ratatui::widgets::Borders;
use ratatui::widgets::Paragraph;
use ratatui::widgets::Widget;
use ratatui::widgets::Wrap;
use tarpc::context;
use tokio::sync::Mutex;

use super::dashboard_app::ConsoleIO;
use super::dashboard_app::DashboardEvent;
use super::overview_screen::VerticalRectifier;
use super::screen::Screen;
use crate::dashboard_rpc_client::DashboardRpcClient;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum SendScreenWidget {
    Address,
    Amount,
    Fee,
    AcceptLustrations,
    Ok,
    Notice,
}

#[derive(Default, Debug, Clone)]
enum ResetType {
    #[default]
    None,
    Form,
    Notice,
}

#[derive(Debug, Clone)]
pub struct SendScreen {
    active: bool,
    fg: Color,
    bg: Color,
    in_focus: bool,
    address: String,
    rpc_client: Arc<DashboardRpcClient>,
    focus: Arc<Mutex<SendScreenWidget>>,
    amount: String,
    fee: String,
    accept_lustrations: bool,
    notice: Arc<Mutex<String>>,
    reset_me: Arc<Mutex<ResetType>>,
    escalatable_event: Arc<std::sync::Mutex<Option<DashboardEvent>>>,
    network: Network,
    token: auth::Token,
}

impl SendScreen {
    pub fn new(rpc_server: Arc<DashboardRpcClient>, network: Network, token: auth::Token) -> Self {
        SendScreen {
            active: false,
            fg: Color::Gray,
            bg: Color::Black,
            in_focus: false,
            address: "".to_string(),
            rpc_client: rpc_server,
            focus: Arc::new(Mutex::new(SendScreenWidget::Address)),
            amount: "".to_string(),
            fee: "".to_string(),
            accept_lustrations: false,
            notice: Arc::new(Mutex::new("".to_string())),
            reset_me: Arc::new(Mutex::new(Default::default())),
            escalatable_event: Arc::new(std::sync::Mutex::new(None)),
            network,
            token,
        }
    }

    #[expect(clippy::too_many_arguments)]
    async fn check_and_pay_sequence(
        rpc_client: Arc<DashboardRpcClient>,
        token: auth::Token,
        address: String,
        amount: String,
        fee: String,
        accept_lustrations: bool,
        notice_arc: Arc<Mutex<String>>,
        reset_me: Arc<Mutex<ResetType>>,
        network: Network,
        refresh_tx: tokio::sync::mpsc::Sender<()>,
    ) {
        const SEND_DEADLINE_IN_SECONDS: u64 = 40;

        *notice_arc.lock().await = "sending ...".to_string();
        refresh_tx.send(()).await.unwrap();

        let valid_amount = match NativeCurrencyAmount::coins_from_str(&amount) {
            Ok(a) => a,
            Err(e) => {
                *notice_arc.lock().await = format!("amount: {e}");
                *reset_me.lock().await = ResetType::Notice;
                refresh_tx.send(()).await.unwrap();
                return;
            }
        };

        let valid_fee = match NativeCurrencyAmount::coins_from_str(&fee) {
            Ok(a) if !a.is_negative() => a,
            Ok(a) => {
                *notice_arc.lock().await = format!("fee: {a}");
                *reset_me.lock().await = ResetType::Notice;
                refresh_tx.send(()).await.unwrap();
                return;
            }
            Err(e) => {
                *notice_arc.lock().await = format!("fee: {e}");
                *reset_me.lock().await = ResetType::Notice;
                refresh_tx.send(()).await.unwrap();
                return;
            }
        };

        let valid_address = match ReceivingAddress::from_bech32m(&address, network) {
            Ok(a) => a,
            Err(e) => {
                *notice_arc.lock().await = format!("address: {e}");
                *reset_me.lock().await = ResetType::Notice;
                refresh_tx.send(()).await.unwrap();
                return;
            }
        };

        // Allow the generation of proves to take some time...
        let mut send_ctx = context::current();
        send_ctx.deadline = SystemTime::now() + Duration::from_secs(SEND_DEADLINE_IN_SECONDS);
        let send_result = rpc_client
            .send(
                send_ctx,
                token,
                vec![OutputFormat::AddressAndAmount(valid_address, valid_amount)],
                ChangePolicy::recover_to_next_unused_key(
                    KeyType::ViewingAddress,
                    UtxoNotificationMedium::OnChain,
                ),
                valid_fee,
                accept_lustrations,
            )
            .await
            .unwrap();

        match send_result {
            Ok(_) => {
                *notice_arc.lock().await = "Payment broadcast".to_string();
                *reset_me.lock().await = ResetType::Form;
            }
            Err(e) => {
                *notice_arc.lock().await = format!("send error.  {e}");
                *reset_me.lock().await = ResetType::Notice;
            }
        }
        refresh_tx.send(()).await.unwrap();
    }

    pub fn handle(
        &mut self,
        event: DashboardEvent,
        refresh_tx: tokio::sync::mpsc::Sender<()>,
    ) -> Option<DashboardEvent> {
        let mut escalate_event = None;
        if let Ok(mut reset_me_mutex_guard) = self.reset_me.try_lock() {
            match *reset_me_mutex_guard {
                ResetType::Form => {
                    self.amount = "".to_string();
                    self.fee = "".to_string();
                    self.address = "".to_string();
                    if let Ok(mut n) = self.notice.try_lock() {
                        *n = "".to_string();
                    }
                }
                ResetType::Notice => {
                    if let Ok(mut n) = self.notice.try_lock() {
                        *n = "".to_string();
                    }
                }
                ResetType::None => {}
            }
            *reset_me_mutex_guard = ResetType::None;
        }
        if self.in_focus {
            match event {
                DashboardEvent::ConsoleEvent(Event::Key(key))
                    if key.kind == KeyEventKind::Press =>
                {
                    match key.code {
                        KeyCode::Enter => {
                            if let Ok(mut own_focus) = self.focus.try_lock() {
                                match own_focus.to_owned() {
                                    SendScreenWidget::Address => {
                                        return Some(DashboardEvent::ConsoleMode(
                                            ConsoleIO::InputRequested(
                                                "Please enter recipient address:\n".to_string(),
                                            ),
                                        ));
                                    }
                                    SendScreenWidget::Amount => {
                                        *own_focus = SendScreenWidget::Fee;
                                        escalate_event = Some(DashboardEvent::RefreshScreen);
                                    }
                                    SendScreenWidget::Fee => {
                                        *own_focus = SendScreenWidget::AcceptLustrations;
                                        escalate_event = Some(DashboardEvent::RefreshScreen);
                                    }
                                    SendScreenWidget::AcceptLustrations => {
                                        self.accept_lustrations = !self.accept_lustrations;
                                        escalate_event = Some(DashboardEvent::RefreshScreen);
                                    }
                                    SendScreenWidget::Ok => {
                                        // clone outside of async section
                                        let rpc_client = self.rpc_client.clone();
                                        let address = self.address.clone();
                                        let amount = self.amount.clone();
                                        let fee = self.fee.clone();
                                        let notice = self.notice.clone();
                                        let reset_me = self.reset_me.clone();
                                        let network = self.network;
                                        let token = self.token;
                                        let accept_lustrations = self.accept_lustrations;

                                        tokio::spawn(Self::check_and_pay_sequence(
                                            rpc_client,
                                            token,
                                            address,
                                            amount,
                                            fee,
                                            accept_lustrations,
                                            notice,
                                            reset_me,
                                            network,
                                            refresh_tx,
                                        ));
                                    }
                                    SendScreenWidget::Notice => {
                                        escalate_event = None;
                                    }
                                }
                            }
                        }
                        KeyCode::Up => {
                            if let Ok(mut own_focus) = self.focus.try_lock() {
                                *own_focus = match own_focus.to_owned() {
                                    SendScreenWidget::Address => SendScreenWidget::Ok,
                                    SendScreenWidget::Amount => SendScreenWidget::Address,
                                    SendScreenWidget::Fee => SendScreenWidget::Amount,
                                    SendScreenWidget::AcceptLustrations => SendScreenWidget::Fee,
                                    SendScreenWidget::Ok => SendScreenWidget::AcceptLustrations,
                                    SendScreenWidget::Notice => SendScreenWidget::Notice,
                                };
                                escalate_event = Some(DashboardEvent::RefreshScreen);
                            } else {
                                escalate_event = Some(event);
                            }
                        }
                        KeyCode::Down => {
                            if let Ok(mut own_focus) = self.focus.try_lock() {
                                *own_focus = match own_focus.to_owned() {
                                    SendScreenWidget::Address => SendScreenWidget::Amount,
                                    SendScreenWidget::Amount => SendScreenWidget::Fee,
                                    SendScreenWidget::Fee => SendScreenWidget::AcceptLustrations,
                                    SendScreenWidget::AcceptLustrations => SendScreenWidget::Ok,
                                    SendScreenWidget::Ok => SendScreenWidget::Address,
                                    SendScreenWidget::Notice => SendScreenWidget::Notice,
                                };
                                escalate_event = Some(DashboardEvent::RefreshScreen);
                            } else {
                                escalate_event = Some(event);
                            }
                        }
                        KeyCode::Char(c) => {
                            if let Ok(own_focus) = self.focus.try_lock() {
                                if own_focus.to_owned() == SendScreenWidget::Amount {
                                    self.amount = format!("{}{}", self.amount, c);
                                    escalate_event = Some(DashboardEvent::RefreshScreen);
                                } else if own_focus.to_owned() == SendScreenWidget::Fee {
                                    self.fee = format!("{}{}", self.fee, c);
                                    escalate_event = Some(DashboardEvent::RefreshScreen);
                                } else {
                                    escalate_event = Some(event);
                                }
                            } else {
                                escalate_event = Some(event);
                            }
                        }
                        KeyCode::Backspace => {
                            if let Ok(own_focus) = self.focus.try_lock() {
                                if own_focus.to_owned() == SendScreenWidget::Amount {
                                    if !self.amount.is_empty() {
                                        self.amount.drain(self.amount.len() - 1..);
                                    }
                                    escalate_event = Some(DashboardEvent::RefreshScreen);
                                }
                                if own_focus.to_owned() == SendScreenWidget::Fee {
                                    if !self.fee.is_empty() {
                                        self.fee.drain(self.fee.len() - 1..);
                                    }
                                    escalate_event = Some(DashboardEvent::RefreshScreen);
                                }
                            } else {
                                escalate_event = Some(event);
                            }
                        }
                        _ => {
                            escalate_event = Some(event);
                        }
                    }
                }
                DashboardEvent::ConsoleMode(ConsoleIO::InputSupplied(string)) => {
                    if let Ok(mut own_focus) = self.focus.try_lock() {
                        string.trim().clone_into(&mut self.address);
                        *own_focus = SendScreenWidget::Amount;
                        escalate_event = Some(DashboardEvent::RefreshScreen);
                    } else {
                        escalate_event = Some(DashboardEvent::ConsoleMode(
                            ConsoleIO::InputSupplied(string),
                        ));
                    }
                }
                _ => {
                    escalate_event = None;
                }
            }
        }
        escalate_event
    }
}

impl Screen for SendScreen {
    fn activate(&mut self) {
        self.active = true;
    }

    fn deactivate(&mut self) {
        self.active = false;
    }

    fn focus(&mut self) {
        self.fg = Color::White;
        self.in_focus = true;
    }

    fn unfocus(&mut self) {
        self.fg = Color::Gray;
        self.in_focus = false;
    }

    fn escalatable_event(&self) -> Arc<std::sync::Mutex<Option<DashboardEvent>>> {
        self.escalatable_event.clone()
    }
}

impl Widget for SendScreen {
    fn render(self, area: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) {
        let own_focus = if let Ok(of) = self.focus.try_lock() {
            of.to_owned()
        } else {
            SendScreenWidget::Notice
        };
        // receive box
        let style: Style = if self.in_focus {
            Style::default().fg(Color::LightCyan).bg(self.bg)
        } else {
            Style::default().fg(Color::Gray).bg(self.bg)
        };
        Block::default()
            .borders(Borders::ALL)
            .title("Send")
            .style(style)
            .render(area, buf);

        // divide the overview box vertically into subboxes,
        // and render each separately
        let style = Style::default().bg(self.bg).fg(self.fg);
        let focus_style = Style::default().bg(self.bg).fg(Color::LightCyan);
        let inner = area.inner(Margin {
            vertical: 1,
            horizontal: 1,
        });
        let width = max(0, inner.width as isize - 2) as usize;
        if width > 0 {
            let mut vrecter = VerticalRectifier::new(inner);

            // display address widget
            let mut address = self.address.clone();
            let mut address_lines = vec![];

            while address.len() > width {
                let (line, remainder) = address.split_at(width);
                address_lines.push(line.to_owned());
                address = remainder.to_owned();
            }
            address_lines.push(address);

            let address_rect = vrecter.next((address_lines.len() + 2).try_into().unwrap());
            if address_rect.height > 0 {
                let address_display = Paragraph::new(Text::from(address_lines.join("\n")))
                    .style(if own_focus == SendScreenWidget::Address && self.in_focus {
                        focus_style
                    } else {
                        style
                    })
                    .block(
                        Block::default()
                            .borders(Borders::ALL)
                            .title(Span::styled("Recipient Address", Style::default())),
                    )
                    .alignment(Alignment::Left);
                address_display.render(address_rect, buf);
            }
            let instruction_rect = vrecter.next(1);
            if instruction_rect.height > 0 {
                let instructions = if self.in_focus && own_focus == SendScreenWidget::Address {
                    Line::from(vec![
                        Span::from("Press "),
                        Span::styled("Enter ↵", Style::default().fg(Color::LightCyan)),
                        Span::from(" to enter address via console mode."),
                    ])
                } else {
                    Line::from(vec![])
                };
                let instructions_widget = Paragraph::new(instructions).style(style);
                instructions_widget.render(instruction_rect, buf);
            }

            // display amount widget
            let amount = self.amount;
            let amount_rect = vrecter.next(3);
            let amount_widget = Paragraph::new(Line::from(vec![
                Span::from(amount),
                if own_focus == SendScreenWidget::Amount {
                    Span::styled(
                        "|",
                        if self.in_focus {
                            Style::default().add_modifier(Modifier::RAPID_BLINK)
                        } else {
                            style
                        },
                    )
                } else {
                    Span::from(" ")
                },
            ]))
            .style(if own_focus == SendScreenWidget::Amount && self.in_focus {
                focus_style
            } else {
                style
            })
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .title("Amount")
                    .style(if own_focus == SendScreenWidget::Amount && self.in_focus {
                        focus_style
                    } else {
                        style
                    }),
            );
            amount_widget.render(amount_rect, buf);

            // display fee widget
            let fee = self.fee;
            let fee_rect = vrecter.next(3);
            let fee_widget = Paragraph::new(Line::from(vec![
                Span::from(fee),
                if own_focus == SendScreenWidget::Fee {
                    Span::styled(
                        "|",
                        if self.in_focus {
                            Style::default().add_modifier(Modifier::RAPID_BLINK)
                        } else {
                            style
                        },
                    )
                } else {
                    Span::from(" ")
                },
            ]))
            .style(if own_focus == SendScreenWidget::Fee && self.in_focus {
                focus_style
            } else {
                style
            })
            .block(Block::default().borders(Borders::ALL).title("Fee").style(
                if own_focus == SendScreenWidget::Fee && self.in_focus {
                    focus_style
                } else {
                    style
                },
            ));
            fee_widget.render(fee_rect, buf);

            // display the lustration widget
            let accept_lustration = self.accept_lustrations;
            let lustration_rect = vrecter.next(1);
            let lustration_icon = if accept_lustration { "[X]" } else { "[ ]" };
            let lustration_text = Line::from(vec![
                Span::styled(
                    format!(" {} ", lustration_icon),
                    if own_focus == SendScreenWidget::AcceptLustrations && self.in_focus {
                        focus_style
                    } else {
                        style
                    },
                ),
                Span::from("Accept lustration"),
            ]);
            let lustration_widget = Paragraph::new(lustration_text).style(
                if own_focus == SendScreenWidget::AcceptLustrations && self.in_focus {
                    focus_style
                } else {
                    style
                },
            );
            lustration_widget.render(lustration_rect, buf);

            // send button
            let mut button_rect = vrecter.next(3);
            button_rect.width = 8;
            let button_widget = Paragraph::new(Span::styled(
                " SEND ",
                if own_focus == SendScreenWidget::Ok && self.in_focus {
                    focus_style
                } else {
                    style
                },
            ))
            .block(Block::default().borders(Borders::ALL).style(
                if own_focus == SendScreenWidget::Ok && self.in_focus {
                    focus_style
                } else {
                    style
                },
            ));
            button_widget.render(button_rect, buf);

            // notice
            if let Ok(notice_text) = self.notice.try_lock() {
                vrecter.next(1);
                let notice_rect = vrecter.next(10);
                let notice_widget = Paragraph::new(notice_text.as_str()).wrap(Wrap { trim: true });
                notice_widget.render(notice_rect, buf);
            }
        }
    }
}
