#![allow(deprecated)] // TODO: Update querybuilder usage to DQL
use std::{
    env, error,
    sync::{Arc, RwLock},
};

use dittolive_ditto::{prelude::*, transport::Peer};
use ratatui::widgets::ListState;

use crate::car::Car;
/// Application result type.
pub type AppResult<T> = std::result::Result<T, Box<dyn error::Error>>;

pub struct TabsState {
    pub titles: Vec<&'static str>,
    pub index: usize,
}

impl TabsState {
    pub fn new(titles: Vec<&'static str>) -> TabsState {
        TabsState { titles, index: 0 }
    }
    pub fn next(&mut self) {
        self.index = (self.index + 1) % self.titles.len();
    }

    pub fn previous(&mut self) {
        if self.index > 0 {
            self.index -= 1;
        } else {
            self.index = self.titles.len() - 1;
        }
    }
}

pub struct StatefulList<T> {
    pub state: ListState,
    pub items: Vec<T>,
}

impl<T> Default for StatefulList<T> {
    fn default() -> Self {
        Self {
            items: vec![],
            state: ListState::default(),
        }
    }
}

impl<T> StatefulList<T> {
    pub fn with_items(items: Vec<T>) -> StatefulList<T> {
        StatefulList {
            state: ListState::default(),
            items,
        }
    }

    pub fn next(&mut self) {
        let i = match self.state.selected() {
            Some(i) => Some((i + 1) % self.items.len()),
            None => None,
        };
        self.state.select(i);
    }

    pub fn previous(&mut self) {
        let i = match self.state.selected() {
            Some(i) => Some(if i == 0 { self.items.len() - 1 } else { i - 1 }),
            None => None,
        };
        self.state.select(i);
    }
}

/// Application.
pub struct App {
    /// Is the application running?
    pub running: bool,
    /// tabs
    pub tabs: TabsState,
    /// cars
    pub cars: Arc<RwLock<StatefulList<Car>>>,
    #[allow(deprecated)] // TODO: Update querybuilder usage to DQL
    pub cars_observer: Option<LiveQuery>,
    #[allow(deprecated)] // TODO: Update querybuilder usage to DQL
    pub cars_subscription: Option<Subscription>,

    /// Other peers
    pub peers: Arc<RwLock<StatefulList<Peer>>>,
    pub peer_observer: Option<PresenceObserver>,
    /// Ditto instance
    pub ditto: Ditto,
    is_sync_started: bool,
    is_cloud_enabled: bool,
}

impl App {
    pub const fn title() -> &'static str {
        "Ditto Cars APP"
    }

    /// Constructs a new instance of [`App`].
    pub fn new(app_id: AppId, shared_token: String) -> Self {
        // Override this auth url and sync url outside of prod
        let auth_url = env::var("DITTO_AUTH_URL").unwrap_or_else(|_x| app_id.default_auth_url());
        let sync_url = env::var("DITTO_SYNC_URL").unwrap_or_else(|_x| app_id.default_sync_url());

        let ditto = Ditto::builder()
            .with_temp_dir()
            .with_identity(|ditto_root| {
                OnlinePlayground::new(ditto_root, app_id, shared_token, false, Some(&auth_url))
            })
            .unwrap()
            .with_transport_config(|_identity| {
                let mut config = TransportConfig::new();
                config.connect.websocket_urls.insert(sync_url);
                config
            })
            .unwrap()
            // prevent logs from disrupting the GUI
            .build()
            .unwrap();
        DittoLogger::set_logging_enabled(false);
        #[allow(deprecated)]
        ditto.set_device_name(&format!("{}", ditto.site_id()));
        let tabs = TabsState::new(vec!["Cars", "Peers"]);

        Self {
            running: true,
            tabs,
            cars: Arc::new(RwLock::new(StatefulList::default())),
            cars_observer: None,
            cars_subscription: None,
            peers: Arc::new(RwLock::new(StatefulList::default())),
            peer_observer: None,
            ditto,
            is_sync_started: false,
            is_cloud_enabled: true,
        }
    }

    pub fn start_pooling(&mut self) {
        self.start_pooling_cars();
        self.start_pooling_peers();

        self.ditto.sync().start().unwrap();
        self.is_sync_started = true;
    }

    #[allow(deprecated)] // TODO: Update querybuilder usage to DQL
    fn start_pooling_cars(&mut self) {
        let collection = self.ditto.store().collection("cars").unwrap();
        let cars = self.cars.clone();

        let cars_observer = collection
            .find_all()
            .observe_local(move |docs: Vec<BoxedDocument>, _event| {
                // render all orders
                let mut cars = cars.write().unwrap();
                cars.items = docs.iter().map(|doc| doc.get("").unwrap()).collect();
                if cars.items.is_empty() {
                    cars.state.select(None);
                } else if cars.state.selected().is_none() {
                    cars.state.select(Some(0));
                }
            })
            .unwrap();
        self.cars_observer = Some(cars_observer);

        let cars_subscription = collection.find_all().subscribe();
        self.cars_subscription = Some(cars_subscription);
    }

    fn start_pooling_peers(&mut self) {
        let peers = self.peers.clone();
        let peer_observer = self
            .ditto
            .presence()
            .register_observer(move |presence_graph| {
                let mut peers = peers.write().unwrap();
                peers.items = presence_graph.remote_peers.clone();

                let new_index = match (peers.items.len(), peers.state.selected()) {
                    // empty list so there is no selection
                    (0, _) => None,
                    // No selection but we received elements => select first
                    (x, None) if x > 0 => Some(0),
                    // If the cursor is above the number of elements, select last one
                    (x, Some(y)) if y >= x => Some(x - 1),
                    // Case where selected element exists, continue
                    (_, Some(y)) => Some(y),
                    // Complement rule for #2
                    (_, None) => None,
                };
                peers.state.select(new_index);
            })
            .unwrap();
        self.peer_observer = Some(peer_observer);
    }

    /// Handles the tick event of the terminal.
    pub fn tick(&self) {}

    /// Set running to false to quit the application.
    pub fn quit(&mut self) {
        self.running = false;
    }

    /// Generate header
    pub fn status_header(&mut self) -> String {
        let icon = |check: bool| {
            if check {
                "✅"
            } else {
                "❌"
            }
        };
        format!(
            "Cloud:{} | Sync:{}",
            icon(self.is_cloud_enabled),
            icon(self.is_sync_started)
        )
    }

    #[allow(deprecated)] // TODO: Update querybuilder usage to DQL
    pub fn add_car(&mut self) {
        let collection = self.ditto.store().collection("cars").unwrap();
        let new_car = Car::generate();
        collection.upsert(new_car).unwrap();
    }

    #[allow(deprecated)] // TODO: Update querybuilder usage to DQL
    pub fn remove_car(&mut self) {
        let mut cars = self.cars.write().unwrap();
        if let Some(index) = cars.state.selected() {
            let car = &cars.items[index];
            let collection = self.ditto.store().collection("cars").unwrap();
            collection
                .find_by_id(car._id.as_ref().unwrap())
                .remove()
                .unwrap();
            // Remove last car
            if index < cars.items.len() {
                cars.state.select(index.checked_sub(1));
            }
        }
    }

    pub fn select_next_car(&mut self) {
        let mut cars = self.cars.write().unwrap();
        cars.next();
    }

    pub fn select_previous_car(&mut self) {
        let mut cars = self.cars.write().unwrap();
        cars.previous();
    }

    pub fn select_next_peer(&mut self) {
        let mut peers = self.peers.write().unwrap();
        peers.next();
    }

    pub fn select_previous_peer(&mut self) {
        let mut peers = self.peers.write().unwrap();
        peers.previous();
    }

    pub fn toggle_sync(&mut self) {
        if self.is_sync_started {
            self.ditto.sync().stop();
        } else {
            self.ditto.sync().start().unwrap();
        }
        self.is_sync_started = !self.is_sync_started;
    }

    pub fn toggle_cloud(&mut self) {
        self.is_cloud_enabled = !self.is_cloud_enabled;
    }

    pub fn toggle_p2p(&mut self) {}
}
