use std::{
    env,
    sync::{Arc, RwLock},
    time::Duration,
};

use dittolive_ditto::{
    dql::QueryResult, fs::TempRoot, prelude::*, presence::Peer, store::StoreObserver,
    sync::SyncSubscription,
};
use ratatui::widgets::ListState;

use crate::car::Car;

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>>>,
    pub cars_observer: Option<Arc<StoreObserver>>,
    pub cars_subscription: Option<Arc<SyncSubscription>>,

    /// Other peers
    pub peers: Arc<RwLock<StatefulList<Peer>>>,
    pub peer_observer: Option<PresenceObserver>,
    /// Ditto instance
    pub ditto: Ditto,
    /// Temporary persistence directory for Ditto; deleted when dropped
    _temp_root: TempRoot,
    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(database_id: DatabaseId, shared_token: String) -> Self {
        // Override this auth url and sync url outside of prod
        let auth_url: url::Url = env::var("DITTO_AUTH_URL")
            .unwrap_or_else(|_| database_id.default_auth_url())
            .parse()
            .expect("Invalid DITTO_AUTH_URL");
        let sync_url =
            env::var("DITTO_SYNC_URL").unwrap_or_else(|_| database_id.default_sync_url());

        let root = TempRoot::new();
        let ditto = Ditto::open_sync(
            DittoConfig::new(
                database_id.to_string(),
                DittoConfigConnect::Server { url: auth_url },
            )
            .with_persistence_directory(root.root_path()),
        )
        .unwrap();

        ditto
            .auth()
            .expect("Auth is available in Server connect mode")
            .set_expiration_handler(move |ditto: &Ditto, _duration: Duration| {
                let token = shared_token.clone();
                let auth = ditto
                    .auth()
                    .expect("Auth is available in Server connect mode");
                async move {
                    if let Err(e) = auth.login(&token, "test-provider") {
                        eprintln!("login failed: {e}");
                    }
                }
            });

        ditto.update_transport_config(|tc| {
            tc.connect.websocket_urls.insert(sync_url);
        });

        // prevent logs from disrupting the GUI
        DittoLogger::set_logging_enabled(false);
        // Set a default device name
        let device_name = &ditto.presence().graph().local_peer.peer_key;
        ditto.set_device_name(&format!("cars-tui-device-{device_name}"));
        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,
            _temp_root: root,
            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;
    }

    fn start_pooling_cars(&mut self) {
        let cars = self.cars.clone();
        let query = "SELECT * FROM cars";

        let cars_observer = self
            .ditto
            .store()
            .register_observer(query, move |result: QueryResult| {
                // render all orders
                let mut cars = cars.write().unwrap();
                cars.items = result
                    .iter()
                    .flat_map(|it| it.deserialize_value::<Car>().ok())
                    .collect::<Vec<_>>();
                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 = self.ditto.sync().register_subscription(query).unwrap();
        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)
        )
    }

    pub async fn add_car(&mut self) {
        let new_car = Car::generate();
        self.ditto
            .store()
            .execute((
                "INSERT INTO cars DOCUMENTS (:car) ON ID CONFLICT DO UPDATE",
                serde_json::json!({ "car": new_car }),
            ))
            .await
            .unwrap();
    }

    pub async fn remove_car(&mut self) {
        let (car, index) = {
            // Scoped lock
            let cars = self.cars.read().unwrap();
            let Some(index) = cars.state.selected() else {
                return;
            };

            (cars.items[index].clone(), index)
        };

        self.ditto
            .store()
            .execute((
                "DELETE FROM cars WHERE _id = :id",
                serde_json::json!({ "id": car._id.as_ref().unwrap() }),
            ))
            .await
            .unwrap();

        // Remove last car
        let mut cars = self.cars.write().unwrap();
        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) {}
}
