use crate::{
    Backend,
    model::{camera::Camera, client::Client},
};
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, types::chrono::NaiveDateTime};

#[derive(FromRow, Debug, Serialize, Deserialize)]
pub struct SightingDb {
    pub client_uuid: String,
    pub sighted_at: NaiveDateTime,
    pub camera_id: String,
}

impl SightingDb {
    async fn to_sighting(&self, backend: &Backend) -> Sighting {
        let client_uuid = uuid::Uuid::parse_str(&self.client_uuid).expect("db constraints");
        let camera_uuid = uuid::Uuid::parse_str(&self.camera_id).expect("db constraints");
        Sighting {
            client: backend.get_client(&client_uuid).await,
            sighted_at: self.sighted_at,
            camera: backend
                .get_camera(&camera_uuid)
                .await
                .expect("db constraints"),
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Sighting {
    pub client: Client,
    pub sighted_at: NaiveDateTime,
    pub camera: Camera,
}

impl Backend {
    pub(crate) async fn sightings_for_client(&self, client: &Client) -> Vec<Sighting> {
        let uuid = client.uuid.to_string();
        match self {
            Backend::Sqlite(db) => {
                let sighting_dbs = sqlx::query_as!(
                    SightingDb,
                    "SELECT client_uuid, sighted_at, camera_id FROM sightings WHERE client_uuid = ?",
                    uuid
                )
                .fetch_all(db)
                .await
                .unwrap();

                // Convert SightingDb to Sighting
                let mut sightings = Vec::new();
                for sighting_db in sighting_dbs {
                    sightings.push(sighting_db.to_sighting(self).await);
                }
                sightings
            }
        }
    }

    pub(crate) async fn client_found_camera(
        &self,
        client: &Client,
        camera: &Camera,
    ) -> Result<i64, ()> {
        let client_uuid = client.uuid.to_string();
        let camera_uuid = camera.uuid.to_string();

        match self {
            Backend::Sqlite(db) => {
                sqlx::query!(
                    "INSERT INTO sightings(client_uuid, camera_id) VALUES (?, ?)",
                    client_uuid,
                    camera_uuid
                )
                .execute(db)
                .await
                .map_err(|_| ())?;

                Ok(sqlx::query_scalar!(
                    "SELECT COUNT(client_uuid) FROM sightings WHERE camera_id = ?",
                    camera_uuid
                )
                .fetch_one(db)
                .await
                .map_err(|_| ())?)
            }
        }
    }
}
