use crate::message::{AddTrack, Message};
use iced::{
    Alignment, Border, Color, Element, Length,
    widget::{Id, button, column, container, pick_list, row, text, text_input},
};
use maolan_engine::message::Action;
use maolan_widgets::numeric_input::number_input;

#[derive(Debug)]
pub struct AddTrackView {
    name: String,
    count: usize,
    audio_ins: usize,
    audio_outs: usize,
    midi_ins: usize,
    midi_outs: usize,
    available_templates: Vec<String>,
    available_group_templates: Vec<String>,
    selected_template: Option<String>,
    selected_template_is_group: bool,
}

impl AddTrackView {
    pub fn name_input_id() -> Id {
        Id::new("add-track-name-input")
    }

    fn create_message(&self) -> Option<Message> {
        if self.name.trim().is_empty() {
            return None;
        }
        Some(Message::AddTrack(AddTrack::Submit))
    }

    pub fn create_messages(&self) -> Vec<Message> {
        let base_name = self.name.trim();
        if base_name.is_empty() {
            return Vec::new();
        }

        let template_name = self
            .selected_template
            .clone()
            .unwrap_or_else(|| "empty".to_string());

        if self.selected_template_is_group {
            return vec![Message::AddGroupFromTemplate {
                base_name: base_name.to_string(),
                template: template_name,
            }];
        }

        let count = self.count.max(1);

        (0..count)
            .map(|index| {
                let name = if count == 1 {
                    base_name.to_string()
                } else {
                    format!("{base_name} {}", index + 1)
                };
                if template_name == "empty" {
                    Message::Request(Action::AddTrack {
                        name,
                        audio_ins: self.audio_ins,
                        midi_ins: self.midi_ins,
                        audio_outs: self.audio_outs,
                        midi_outs: self.midi_outs,
                    })
                } else {
                    Message::AddTrackFromTemplate {
                        name,
                        template: template_name.clone(),
                        audio_ins: self.audio_ins,
                        midi_ins: self.midi_ins,
                        audio_outs: self.audio_outs,
                        midi_outs: self.midi_outs,
                    }
                }
            })
            .collect()
    }

    pub fn set_available_templates(&mut self, templates: Vec<String>) {
        self.available_templates = templates;
    }

    pub fn set_available_group_templates(&mut self, templates: Vec<String>) {
        self.available_group_templates = templates;
    }

    fn load_template_config(template_name: &str) -> Option<(usize, usize, usize, usize)> {
        use std::fs::File;
        use std::io::BufReader;

        let home = std::env::var("HOME").ok()?;
        let template_path = format!(
            "{}/.config/maolan/track_templates/{}/track.json",
            home, template_name
        );

        let file = File::open(template_path).ok()?;
        let reader = BufReader::new(file);
        let json: serde_json::Value = serde_json::from_reader(reader).ok()?;

        let track = json.get("track")?;
        let audio = track.get("audio")?;
        let midi = track.get("midi")?;

        let audio_ins = audio.get("ins")?.as_u64()? as usize;
        let audio_outs = audio.get("outs")?.as_u64()? as usize;
        let midi_ins = midi.get("ins")?.as_u64()? as usize;
        let midi_outs = midi.get("outs")?.as_u64()? as usize;

        Some((audio_ins, audio_outs, midi_ins, midi_outs))
    }

    fn load_group_template_config(template_name: &str) -> Option<usize> {
        use std::fs::File;
        use std::io::BufReader;

        let home = std::env::var("HOME").ok()?;
        let template_path = format!(
            "{}/.config/maolan/group_templates/{}/group.json",
            home, template_name
        );

        let file = File::open(template_path).ok()?;
        let reader = BufReader::new(file);
        let json: serde_json::Value = serde_json::from_reader(reader).ok()?;

        let tracks = json.get("tracks")?.as_array()?;
        Some(tracks.len())
    }

    pub fn update(&mut self, message: &Message) {
        if let Message::AddTrack(a) = message {
            match a {
                AddTrack::Name(name) => {
                    self.name = name.clone();
                }
                AddTrack::Count(count) => {
                    self.count = (*count).max(1);
                }
                AddTrack::AudioIns(ins) => {
                    self.audio_ins = *ins;
                }
                AddTrack::MIDIIns(ins) => {
                    self.midi_ins = *ins;
                }
                AddTrack::AudioOuts(outs) => {
                    self.audio_outs = *outs;
                }
                AddTrack::MIDIOuts(outs) => {
                    self.midi_outs = *outs;
                }
                AddTrack::TemplateSelected(template) => {
                    if template == "empty" {
                        self.selected_template = Some(template.clone());
                        self.selected_template_is_group = false;

                        self.audio_ins = 1;
                        self.audio_outs = 1;
                        self.midi_ins = 0;
                        self.midi_outs = 0;
                    } else {
                        self.selected_template = Some(template.clone());

                        if let Some((audio_ins, audio_outs, midi_ins, midi_outs)) =
                            Self::load_template_config(template)
                        {
                            self.selected_template_is_group = false;
                            self.audio_ins = audio_ins;
                            self.audio_outs = audio_outs;
                            self.midi_ins = midi_ins;
                            self.midi_outs = midi_outs;
                        } else if Self::load_group_template_config(template).is_some() {
                            self.selected_template_is_group = true;
                        }
                    }
                }
                AddTrack::Submit => {}
            }
        }
    }

    pub fn view(&self) -> Element<'_, Message> {
        let create_message = self.create_message();
        let create = if let Some(message) = create_message.clone() {
            button("Create").on_press(message)
        } else {
            button("Create")
        };

        let mut template_options = vec!["empty".to_string()];
        template_options.extend(self.available_templates.clone());
        for group in &self.available_group_templates {
            template_options.push(format!("{} (group)", group));
        }

        let selected_display = self.selected_template.as_deref().unwrap_or("empty");
        let is_empty_template = selected_display == "empty";
        let is_group_template = self.selected_template_is_group;

        let mut col = column![
            row![
                text("Template:"),
                pick_list(
                    template_options,
                    Some(if is_group_template {
                        format!("{} (group)", selected_display)
                    } else {
                        selected_display.to_string()
                    }),
                    |template| {
                        let stripped = template
                            .strip_suffix(" (group)")
                            .map(|s| s.to_string())
                            .unwrap_or(template);
                        Message::AddTrack(AddTrack::TemplateSelected(stripped))
                    }
                )
                .width(Length::Fixed(200.0)),
            ]
            .spacing(10),
            row![
                text("Name:"),
                text_input("Track name", &self.name)
                    .id(Self::name_input_id())
                    .on_input(|name: String| Message::AddTrack(AddTrack::Name(name)))
                    .on_submit_maybe(create_message)
                    .width(Length::Fixed(200.0)),
            ]
            .spacing(10),
        ];

        if !is_group_template {
            col = col.push(
                row![
                    text("Tracks:"),
                    number_input(&self.count, 1..=128, |count: usize| {
                        Message::AddTrack(AddTrack::Count(count))
                    })
                ]
                .spacing(10),
            );
        }

        if is_empty_template {
            col = col.push(
                row![
                    text("Audio inputs:"),
                    number_input(&self.audio_ins, 0..=32, |ins: usize| {
                        Message::AddTrack(AddTrack::AudioIns(ins))
                    })
                ]
                .spacing(10),
            );
            col = col.push(
                row![
                    text("Audio outputs:"),
                    number_input(&self.audio_outs, 0..=32, |outs: usize| {
                        Message::AddTrack(AddTrack::AudioOuts(outs))
                    }),
                ]
                .spacing(10),
            );
            col = col.push(
                row![
                    text("Midi inputs:"),
                    number_input(&self.midi_ins, 0..=32, |ins: usize| {
                        Message::AddTrack(AddTrack::MIDIIns(ins))
                    })
                ]
                .spacing(10),
            );
            col = col.push(
                row![
                    text("Midi outputs:"),
                    number_input(&self.midi_outs, 0..=32, |outs: usize| {
                        Message::AddTrack(AddTrack::MIDIOuts(outs))
                    }),
                ]
                .spacing(10),
            );
        } else if is_group_template {
            let track_count = Self::load_group_template_config(selected_display).unwrap_or(0);
            col = col
                .push(row![text(format!("Group template: {} tracks", track_count)),].spacing(10));
        } else {
            col = col.push(
                row![text(format!(
                    "Audio: {} in / {} out, MIDI: {} in / {} out",
                    self.audio_ins, self.audio_outs, self.midi_ins, self.midi_outs
                )),]
                .spacing(10),
            );
        }

        col = col.push(
            row![
                create,
                button("Cancel")
                    .on_press(Message::Cancel)
                    .style(button::secondary)
            ]
            .spacing(10),
        );

        container(column![text("Add Track"), col.align_x(Alignment::End).spacing(10)].spacing(10))
            .style(|_theme| container::Style {
                border: Border {
                    color: Color::from_rgba(0.34, 0.42, 0.56, 0.72),
                    width: 1.0,
                    ..Border::default()
                },
                ..crate::style::app_background()
            })
            .padding(12)
            .width(Length::Fixed(320.0))
            .height(Length::Fill)
            .into()
    }
}

impl Default for AddTrackView {
    fn default() -> Self {
        Self {
            count: 1,
            audio_ins: 1,
            audio_outs: 1,
            midi_ins: 0,
            midi_outs: 0,
            name: "".to_string(),
            available_templates: vec![],
            available_group_templates: vec![],
            selected_template: Some("empty".to_string()),
            selected_template_is_group: false,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::{
        fs,
        sync::{LazyLock, Mutex},
        time::{SystemTime, UNIX_EPOCH},
    };

    static ENV_GUARD: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));

    #[test]
    fn update_applies_basic_add_track_fields() {
        let mut view = AddTrackView::default();

        view.update(&Message::AddTrack(AddTrack::Name("Lead".to_string())));
        view.update(&Message::AddTrack(AddTrack::Count(3)));
        view.update(&Message::AddTrack(AddTrack::AudioIns(2)));
        view.update(&Message::AddTrack(AddTrack::AudioOuts(4)));
        view.update(&Message::AddTrack(AddTrack::MIDIIns(1)));
        view.update(&Message::AddTrack(AddTrack::MIDIOuts(5)));

        assert_eq!(view.name, "Lead");
        assert_eq!(view.count, 3);
        assert_eq!(view.audio_ins, 2);
        assert_eq!(view.audio_outs, 4);
        assert_eq!(view.midi_ins, 1);
        assert_eq!(view.midi_outs, 5);
    }

    #[test]
    fn update_clamps_track_count_to_at_least_one() {
        let mut view = AddTrackView::default();

        view.update(&Message::AddTrack(AddTrack::Count(0)));

        assert_eq!(view.count, 1);
    }

    #[test]
    fn update_selecting_empty_template_resets_default_io() {
        let mut view = AddTrackView {
            audio_ins: 7,
            audio_outs: 8,
            midi_ins: 9,
            midi_outs: 10,
            ..AddTrackView::default()
        };

        view.update(&Message::AddTrack(AddTrack::TemplateSelected(
            "empty".to_string(),
        )));

        assert_eq!(view.selected_template.as_deref(), Some("empty"));
        assert_eq!(view.audio_ins, 1);
        assert_eq!(view.audio_outs, 1);
        assert_eq!(view.midi_ins, 0);
        assert_eq!(view.midi_outs, 0);
        assert!(!view.selected_template_is_group);
    }

    #[test]
    fn update_selecting_template_loads_io_from_template_file() {
        let _guard = ENV_GUARD.lock().expect("lock env guard");
        let unique = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_nanos();
        let temp_home = std::env::temp_dir().join(format!("maolan_add_track_test_{unique}"));
        let template_dir = temp_home.join(".config/maolan/track_templates/Band");
        fs::create_dir_all(&template_dir).expect("create template dir");
        fs::write(
            template_dir.join("track.json"),
            r#"{"track":{"audio":{"ins":2,"outs":3},"midi":{"ins":4,"outs":5}}}"#,
        )
        .expect("write template file");

        let old_home = std::env::var("HOME").ok();
        unsafe {
            std::env::set_var("HOME", &temp_home);
        }

        let mut view = AddTrackView::default();
        view.update(&Message::AddTrack(AddTrack::TemplateSelected(
            "Band".to_string(),
        )));

        if let Some(home) = old_home {
            unsafe {
                std::env::set_var("HOME", home);
            }
        } else {
            unsafe {
                std::env::remove_var("HOME");
            }
        }
        let _ = fs::remove_dir_all(&temp_home);

        assert_eq!(view.selected_template.as_deref(), Some("Band"));
        assert_eq!(view.audio_ins, 2);
        assert_eq!(view.audio_outs, 3);
        assert_eq!(view.midi_ins, 4);
        assert_eq!(view.midi_outs, 5);
        assert!(!view.selected_template_is_group);
    }

    #[test]
    fn update_selecting_group_template_sets_flag() {
        let _guard = ENV_GUARD.lock().expect("lock env guard");
        let unique = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_nanos();
        let temp_home = std::env::temp_dir().join(format!("maolan_add_track_test_{unique}"));
        let template_dir = temp_home.join(".config/maolan/group_templates/Drums");
        fs::create_dir_all(&template_dir).expect("create template dir");
        fs::write(
            template_dir.join("group.json"),
            r#"{"tracks":[{"track":{"name":"Kick","audio":{"ins":1,"outs":1},"midi":{"ins":0,"outs":0}}}]}"#,
        )
        .expect("write group template file");

        let old_home = std::env::var("HOME").ok();
        unsafe {
            std::env::set_var("HOME", &temp_home);
        }

        let mut view = AddTrackView::default();
        view.update(&Message::AddTrack(AddTrack::TemplateSelected(
            "Drums".to_string(),
        )));

        if let Some(home) = old_home {
            unsafe {
                std::env::set_var("HOME", home);
            }
        } else {
            unsafe {
                std::env::remove_var("HOME");
            }
        }
        let _ = fs::remove_dir_all(&temp_home);

        assert_eq!(view.selected_template.as_deref(), Some("Drums"));
        assert!(view.selected_template_is_group);
    }

    #[test]
    fn create_messages_returns_group_message_for_group_template() {
        let view = AddTrackView {
            name: "MyDrums".to_string(),
            selected_template: Some("Drums".to_string()),
            selected_template_is_group: true,
            ..Default::default()
        };

        let messages = view.create_messages();
        assert_eq!(messages.len(), 1);
        assert!(
            matches!(&messages[0], Message::AddGroupFromTemplate { base_name, template } if base_name == "MyDrums" && template == "Drums")
        );
    }

    #[test]
    fn update_ignores_non_add_track_messages() {
        let mut view = AddTrackView::default();

        view.update(&Message::Cancel);

        assert_eq!(view.name, "");
        assert_eq!(view.count, 1);
        assert_eq!(view.selected_template.as_deref(), Some("empty"));
        assert!(!view.selected_template_is_group);
    }

    #[test]
    fn default_creates_view() {
        let view = AddTrackView::default();
        assert_eq!(view.name, "");
        assert_eq!(view.count, 1);
        assert_eq!(view.audio_ins, 1);
        assert_eq!(view.audio_outs, 1);
        assert_eq!(view.midi_ins, 0);
        assert_eq!(view.midi_outs, 0);
        assert!(!view.selected_template_is_group);
    }

    #[test]
    fn name_input_id_returns_expected_id() {
        let id = AddTrackView::name_input_id();
        let _ = &id;
    }

    #[test]
    fn create_message_returns_none_when_name_empty() {
        let view = AddTrackView::default();
        assert!(view.create_message().is_none());
    }

    #[test]
    fn create_messages_returns_empty_when_name_empty() {
        let view = AddTrackView::default();
        assert!(view.create_messages().is_empty());
    }

    #[test]
    #[allow(clippy::field_reassign_with_default)]
    fn create_messages_returns_messages_when_name_set() {
        let mut view = AddTrackView::default();
        view.name = "Test Track".to_string();
        view.count = 2;

        let messages = view.create_messages();
        assert_eq!(messages.len(), 2);
    }

    #[test]
    fn set_available_templates_updates_list() {
        let mut view = AddTrackView::default();
        view.set_available_templates(vec!["Template1".to_string(), "Template2".to_string()]);
        view.set_available_group_templates(vec!["Group1".to_string()]);
        assert_eq!(view.available_templates.len(), 2);
        assert_eq!(view.available_group_templates.len(), 1);
    }
}
