use crate::{
    common::{AppTime, AppTimeFormat, Style},
    constants::TICK_VALUE_MS,
    duration::{DurationEx, MAX_DURATION},
    events::{AppEventTx, TuiEvent, TuiEventHandler},
    widgets::{
        clock::{self, ClockState, ClockStateArgs, ClockWidget, Mode as ClockMode},
        edit_time::{EditTimeState, EditTimeStateArgs, EditTimeWidget},
    },
};
use crossterm::event::{Event as CrosstermEvent, KeyModifiers};
use ratatui::{
    buffer::Buffer,
    crossterm::event::KeyCode,
    layout::{Constraint, Layout, Rect},
    text::Line,
    widgets::{StatefulWidget, Widget},
};
use std::ops::Sub;
use std::{cmp::max, time::Duration};
use time::OffsetDateTime;

pub struct CountdownStateArgs {
    pub initial_value: Duration,
    pub current_value: Duration,
    pub elapsed_value: Duration,
    pub app_time: AppTime,
    pub target_time_format: Option<AppTimeFormat>,
    pub with_decis: bool,
    pub app_tx: AppEventTx,
    pub vim_motions: bool,
}

/// State for Countdown Widget
pub struct CountdownState {
    /// clock to count down
    clock: ClockState<clock::Countdown>,
    /// clock to count time after `DONE` - similar to Mission Elapsed Time (MET)
    elapsed_clock: ClockState<clock::Timer>,
    app_time: AppTime,
    /// target time format
    target_time_format: Option<AppTimeFormat>,
    /// target time coundown will finish
    target_time: OffsetDateTime,
    /// Edit by local time
    edit_time: Option<EditTimeState>,
    /// Whether Vim motions are enabled
    vim_motions: bool,
}

impl CountdownState {
    pub fn new(args: CountdownStateArgs) -> Self {
        let CountdownStateArgs {
            initial_value,
            current_value,
            elapsed_value,
            with_decis,
            app_time,
            target_time_format: app_time_format,
            app_tx,
            vim_motions,
        } = args;

        Self {
            clock: ClockState::<clock::Countdown>::new(ClockStateArgs {
                initial_value,
                current_value,
                tick_value: Duration::from_millis(TICK_VALUE_MS),
                with_decis,
                app_tx: Some(app_tx.clone()),
            }),
            elapsed_clock: ClockState::<clock::Timer>::new(ClockStateArgs {
                initial_value: Duration::ZERO,
                current_value: elapsed_value,
                tick_value: Duration::from_millis(TICK_VALUE_MS),
                with_decis: false,
                app_tx: None,
            })
            .with_name("MET".to_owned())
            // A previous `elapsed_value > 0` means the `Clock` was running before,
            // but not in `Initial` state anymore. Updating `Mode` here
            // is needed to handle `Event::Tick` in `EventHandler::update` properly
            .with_mode(if elapsed_value.gt(&Duration::ZERO) {
                ClockMode::Pause
            } else {
                ClockMode::Initial
            }),
            app_time,
            target_time_format: app_time_format,
            target_time: OffsetDateTime::from(app_time),
            edit_time: None,
            vim_motions,
        }
    }

    pub fn set_with_decis(&mut self, with_decis: bool) {
        self.clock.with_decis = with_decis;
        self.elapsed_clock.with_decis = with_decis;
    }

    pub fn get_clock(&self) -> &ClockState<clock::Countdown> {
        &self.clock
    }

    pub fn is_running(&self) -> bool {
        self.clock.is_running() || self.elapsed_clock.is_running()
    }

    pub fn get_elapsed_value(&self) -> &DurationEx {
        self.elapsed_clock.get_current_value()
    }

    pub fn set_app_time(&mut self, app_time: AppTime) {
        self.app_time = app_time;
    }

    pub fn set_app_time_format(&mut self, value: Option<AppTimeFormat>) {
        self.target_time_format = value;
    }

    fn time_to_edit(&self) -> OffsetDateTime {
        // get current value
        let d: Duration = (*self.clock.get_current_value()).into();
        // transform
        let dd = time::Duration::try_from(d).unwrap_or(time::Duration::ZERO);
        // substract from `app_time`
        OffsetDateTime::from(self.app_time).saturating_add(dd)
    }

    pub fn min_time_to_edit(&self) -> OffsetDateTime {
        OffsetDateTime::from(self.app_time)
    }

    fn max_time_to_edit(&self) -> OffsetDateTime {
        OffsetDateTime::from(self.app_time)
            .saturating_add(time::Duration::try_from(MAX_DURATION).unwrap_or(time::Duration::ZERO))
    }

    fn edit_time_done(&mut self, edit_time: &mut EditTimeState) {
        // get diff
        let d: time::Duration = edit_time
            .get_time()
            .sub(OffsetDateTime::from(self.app_time));
        // transfrom
        let dx: DurationEx = Duration::try_from(d).unwrap_or(Duration::ZERO).into();
        // update clock
        self.clock.set_current_value(dx);
        // remove `edit_time`
        self.edit_time = None;
    }

    pub fn is_clock_edit_mode(&self) -> bool {
        self.clock.is_edit_mode()
    }

    pub fn is_time_edit_mode(&self) -> bool {
        self.edit_time.is_some()
    }
}

impl TuiEventHandler for CountdownState {
    fn update(&mut self, event: TuiEvent) -> Option<TuiEvent> {
        match event {
            TuiEvent::Tick => {
                if !self.clock.is_done() {
                    self.clock.tick();
                    self.target_time = self.time_to_edit();
                } else {
                    self.clock.update_done_count();
                    self.elapsed_clock.tick();
                    if self.elapsed_clock.is_initial() {
                        self.elapsed_clock.run();
                    }
                }
                let min_time = self.min_time_to_edit();
                let max_time = self.max_time_to_edit();
                if let Some(edit_time) = &mut self.edit_time {
                    edit_time.set_min_time(min_time);
                    edit_time.set_max_time(max_time);
                }
            }
            // EDIT CLOCK mode
            TuiEvent::Crossterm(CrosstermEvent::Key(key)) if self.is_clock_edit_mode() => {
                match key.code {
                    // skip editing
                    KeyCode::Esc => {
                        // Important: set current value first
                        self.clock.set_current_value(*self.clock.get_prev_value());
                        // before toggling back to non-edit mode
                        self.clock.toggle_edit();
                    }
                    // Apply changes and set new initial value
                    KeyCode::Char('s') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                        // toggle edit mode
                        self.clock.toggle_edit();
                        // set initial value
                        self.clock
                            .set_initial_value(*self.clock.get_current_value());
                        // always reset `elapsed_clock`
                        self.elapsed_clock.reset();
                    }
                    // Apply changes
                    KeyCode::Char('s') => {
                        // toggle edit mode
                        self.clock.toggle_edit();
                        // always reset `elapsed_clock`
                        self.elapsed_clock.reset();
                    }
                    KeyCode::Right if !self.vim_motions => {
                        self.clock.edit_prev();
                    }
                    KeyCode::Char('l') if self.vim_motions => {
                        self.clock.edit_prev();
                    }
                    KeyCode::Left if !self.vim_motions => {
                        self.clock.edit_next();
                    }
                    KeyCode::Char('h') if self.vim_motions => {
                        self.clock.edit_next();
                    }
                    KeyCode::Up if key.modifiers.contains(KeyModifiers::CONTROL) => {
                        self.clock.edit_jump_up();
                    }
                    KeyCode::Char('k')
                        if key.modifiers.contains(KeyModifiers::CONTROL) && self.vim_motions =>
                    {
                        self.clock.edit_jump_up();
                    }
                    KeyCode::Up if !self.vim_motions => {
                        self.clock.edit_up();
                    }
                    KeyCode::Char('k') if self.vim_motions => {
                        self.clock.edit_up();
                    }
                    KeyCode::Down if key.modifiers.contains(KeyModifiers::CONTROL) => {
                        self.clock.edit_jump_down();
                    }
                    KeyCode::Char('j')
                        if key.modifiers.contains(KeyModifiers::CONTROL) && self.vim_motions =>
                    {
                        self.clock.edit_jump_down();
                    }
                    KeyCode::Down if !self.vim_motions => {
                        self.clock.edit_down();
                    }
                    KeyCode::Char('j') if self.vim_motions => {
                        self.clock.edit_down();
                    }
                    _ => return Some(event),
                }
            }
            // EDIT LOCAL TIME mode
            TuiEvent::Crossterm(CrosstermEvent::Key(key)) if self.is_time_edit_mode() => {
                match key.code {
                    // skip editing
                    KeyCode::Esc => {
                        self.edit_time = None;
                    }
                    // Apply changes and set new initial value
                    KeyCode::Char('s') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                        if let Some(edit_time) = &mut self.edit_time.clone() {
                            // Order matters:
                            // 1. update current value
                            self.edit_time_done(edit_time);
                            // 2. set initial value
                            self.clock
                                .set_initial_value(*self.clock.get_current_value());
                        }
                        // always reset `elapsed_clock`
                        self.elapsed_clock.reset();
                    }
                    // Apply changes of editing by local time
                    KeyCode::Char('s') => {
                        if let Some(edit_time) = &mut self.edit_time.clone() {
                            self.edit_time_done(edit_time)
                        }
                        // always reset `elapsed_clock`
                        self.elapsed_clock.reset();
                    }
                    // move edit position to the left
                    KeyCode::Left if !self.vim_motions => {
                        // safe unwrap because we are in `is_time_edit_mode`
                        self.edit_time.as_mut().unwrap().next();
                    }
                    KeyCode::Char('h') if self.vim_motions => {
                        // safe unwrap because we are in `is_time_edit_mode`
                        self.edit_time.as_mut().unwrap().next();
                    }
                    // move edit position to the right
                    KeyCode::Right if !self.vim_motions => {
                        // safe unwrap because we are in `is_time_edit_mode`
                        self.edit_time.as_mut().unwrap().prev();
                    }
                    KeyCode::Char('l') if self.vim_motions => {
                        // safe unwrap because we are in `is_time_edit_mode`
                        self.edit_time.as_mut().unwrap().prev();
                    }
                    // change value up
                    KeyCode::Up if key.modifiers.contains(KeyModifiers::CONTROL) => {
                        // safe unwrap because of previous check in `is_time_edit_mode`
                        self.edit_time.as_mut().unwrap().jump_up();
                    }
                    KeyCode::Char('k')
                        if key.modifiers.contains(KeyModifiers::CONTROL) && self.vim_motions =>
                    {
                        // safe unwrap because of previous check in `is_time_edit_mode`
                        self.edit_time.as_mut().unwrap().jump_up();
                    }
                    KeyCode::Up if !self.vim_motions => {
                        // safe unwrap because of previous check in `is_time_edit_mode`
                        self.edit_time.as_mut().unwrap().up();
                    }
                    KeyCode::Char('k') if self.vim_motions => {
                        // safe unwrap because of previous check in `is_time_edit_mode`
                        self.edit_time.as_mut().unwrap().up();
                    }
                    // change value down
                    KeyCode::Down if key.modifiers.contains(KeyModifiers::CONTROL) => {
                        // safe unwrap because of previous check in `is_time_edit_mode`
                        self.edit_time.as_mut().unwrap().jump_down();
                    }
                    KeyCode::Char('j')
                        if key.modifiers.contains(KeyModifiers::CONTROL) && self.vim_motions =>
                    {
                        // safe unwrap because of previous check in `is_time_edit_mode`
                        self.edit_time.as_mut().unwrap().jump_down();
                    }
                    KeyCode::Down if !self.vim_motions => {
                        // safe unwrap because of previous check in `is_time_edit_mode`
                        self.edit_time.as_mut().unwrap().down();
                    }
                    KeyCode::Char('j') if self.vim_motions => {
                        // safe unwrap because of previous check in `is_time_edit_mode`
                        self.edit_time.as_mut().unwrap().down();
                    }
                    _ => return Some(event),
                }
            }
            // default mode
            TuiEvent::Crossterm(CrosstermEvent::Key(key)) => match key.code {
                KeyCode::Char('r') => {
                    // reset both clocks to use intial values
                    self.clock.reset();
                    self.elapsed_clock.reset();

                    // reset `edit_time` back initial value
                    let time = self.time_to_edit();
                    if let Some(edit_time) = &mut self.edit_time {
                        edit_time.set_time(time);
                    }
                }
                KeyCode::Char(' ') => {
                    // toggle pause status depending on which clock is running
                    if !self.clock.is_done() {
                        self.clock.toggle_pause();
                    } else {
                        self.elapsed_clock.toggle_pause();
                    }

                    // finish `edit_time` and continue for using `clock`
                    if let Some(edit_time) = &mut self.edit_time.clone() {
                        self.edit_time_done(edit_time);
                    }
                }
                // Enter edit by local time mode
                KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                    // set `edit_time`
                    self.edit_time = Some(EditTimeState::new(EditTimeStateArgs {
                        time: self.time_to_edit(),
                        min: self.min_time_to_edit(),
                        max: self.max_time_to_edit(),
                    }));

                    // pause `elapsed_clock`
                    if self.elapsed_clock.is_running() {
                        self.elapsed_clock.toggle_pause();
                    }
                }
                // Enter edit clock mode
                KeyCode::Char('e') => {
                    // toggle edit mode
                    self.clock.toggle_edit();

                    // pause `elapsed_clock`
                    if self.elapsed_clock.is_running() {
                        self.elapsed_clock.toggle_pause();
                    }
                }
                _ => return Some(event),
            },
            _ => return Some(event),
        }
        None
    }
}

pub struct Countdown {
    pub style: Style,
    pub blink: bool,
}

fn human_days_diff(a: &OffsetDateTime, b: &OffsetDateTime) -> String {
    let days_diff = (a.date() - b.date()).whole_days();
    match days_diff {
        0 => "today".to_owned(),
        1 => "tomorrow".to_owned(),
        n => format!("+{n}days"),
    }
}

impl StatefulWidget for Countdown {
    type State = CountdownState;
    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
        // render `edit_time` OR `clock`
        if let Some(edit_time) = &mut state.edit_time {
            let label = Line::raw(
                format!(
                    "Countdown {} {}",
                    edit_time.get_selected().clone(),
                    human_days_diff(edit_time.get_time(), &state.app_time.into())
                )
                .to_uppercase(),
            );
            let widget = EditTimeWidget::new(self.style);
            let area = area.centered(
                Constraint::Length(max(widget.get_width(), label.width() as u16)),
                Constraint::Length(
                    // 1 = height of `label`
                    widget.get_height() + 1,
                ),
            );
            let [v1, v2] =
                Layout::vertical(Constraint::from_lengths([widget.get_height(), 1])).areas(area);

            widget.render(v1, buf, edit_time);
            label.centered().render(v2, buf);
        } else {
            let label = Line::raw(
                if state.clock.is_done() {
                    if state.clock.with_decis {
                        format!(
                            "Countdown {} +{}",
                            state.clock.get_mode(),
                            state
                                .elapsed_clock
                                .get_current_value()
                                .to_string_with_decis()
                        )
                    } else {
                        format!(
                            "Countdown {} +{}",
                            state.clock.get_mode(),
                            state.elapsed_clock.get_current_value()
                        )
                    }
                } else {
                    format!("Countdown {}", state.clock.get_mode())
                }
                .to_uppercase(),
            );
            let widget = ClockWidget::new(self.style, self.blink);
            let label_target_time = Line::raw(
                if let Some(tf) = state.target_time_format
                    // hide target time if we edit by time - no duplication of information then
                    && !state.is_time_edit_mode()
                {
                    format!("Finish {}", AppTime::Local(state.target_time).format(&tf))
                } else {
                    " ".to_owned()
                }
                .to_uppercase(),
            );

            let area = area.centered(
                Constraint::Length(max(
                    max(
                        widget.get_width(state.clock.get_format(), state.clock.with_decis),
                        label.width() as u16,
                    ),
                    label_target_time.width() as u16,
                )),
                Constraint::Length(
                    // 3 = heights of empty label + `label` + `label_target_time`
                    widget.get_height() + 3,
                ),
            );
            let [v0, v1, v2, v3] =
                Layout::vertical(Constraint::from_lengths([1, widget.get_height(), 1, 1]))
                    .areas(area);

            Line::raw("").centered().render(v0, buf);
            widget.render(v1, buf, &mut state.clock);
            label.centered().render(v2, buf);
            label_target_time.centered().render(v3, buf);
        }
    }
}
