use std::collections::HashMap;

use crate::model::user::User;
use chrono::Datelike;
use serde::Serialize;
use sqlx::{FromRow, Row, SqlitePool};

use super::boat::Boat;

#[derive(Serialize, Clone)]
pub struct BoatStat {
    pot_years: Vec<i32>,
    boats: Vec<SingleBoatStat>,
}

#[derive(Serialize, Clone)]
pub struct SingleBoatStat {
    name: String,
    location: String,
    years: HashMap<String, i32>,
}

impl BoatStat {
    pub async fn get(db: &SqlitePool) -> BoatStat {
        let mut years = Vec::new();
        let mut boat_stats_map: HashMap<String, SingleBoatStat> = HashMap::new();

        let rows = sqlx::query(
            "
SELECT 
    boat.id, 
    location.name AS location,
    CAST(strftime('%Y', COALESCE(arrival, 'now')) AS INTEGER) AS year, 
    CAST(SUM(COALESCE(distance_in_km, 0)) AS INTEGER) AS rowed_km
FROM 
    boat
LEFT JOIN 
    logbook ON boat.id = logbook.boat_id AND logbook.arrival IS NOT NULL
LEFT JOIN 
    location ON boat.location_id = location.id
WHERE 
    boat.name != 'Externes Boot'
GROUP BY 
    boat.id, year
ORDER BY 
    boat.name, year DESC;
        ",
        )
        .fetch_all(db)
        .await
        .unwrap();

        for row in rows {
            let id: i32 = row.get("id");
            let boat = Boat::find_by_id(db, id).await.unwrap();
            let owner = if let Some(owner) = boat.owner(db).await {
                owner.name
            } else {
                String::from("Verein")
            };
            let name = format!("{} ({})", boat.name, owner);
            let location: String = row.get("location");
            let year: i32 = row.get("year");
            if year == 0 {
                continue; // Boat still on water
            }

            if !years.contains(&year) {
                years.push(year);
            }

            let year: String = format!("{year}");

            let rowed_km: i32 = row.get("rowed_km");

            let boat_stat = boat_stats_map
                .entry(name.clone())
                .or_insert(SingleBoatStat {
                    name,
                    location,
                    years: HashMap::new(),
                });
            boat_stat.years.insert(year, rowed_km);
        }

        BoatStat {
            pot_years: years,
            boats: boat_stats_map.into_values().collect(),
        }
    }
}

#[derive(FromRow, Serialize, Clone)]
pub struct Stat {
    name: String,
    rowed_km: i32,
}

impl Stat {
    pub async fn guest(db: &SqlitePool, year: Option<i32>) -> Stat {
        let year = match year {
            Some(year) => year,
            None => chrono::Local::now().year(),
        };
        //TODO: switch to query! macro again (once upgraded to sqlite 3.42 on server)
        let rowed_km = sqlx::query(&format!(
            "
SELECT SUM((b.amount_seats - COALESCE(m.member_count, 0)) * l.distance_in_km) as total_guest_km
FROM logbook l
JOIN boat b ON l.boat_id = b.id
LEFT JOIN (
    SELECT logbook_id, COUNT(*) as member_count
    FROM rower
    GROUP BY logbook_id
) m ON l.id = m.logbook_id
WHERE l.distance_in_km IS NOT NULL AND l.arrival LIKE '{year}-%' AND b.name != 'Externes Boot';
"
        ))
        .fetch_one(db)
        .await
        .unwrap()
        .get::<i64, usize>(0) as i32;

        let rowed_km_guests = sqlx::query(&format!(
            "
SELECT CAST(SUM(l.distance_in_km) AS INTEGER) AS rowed_km
FROM user u
INNER JOIN rower r ON u.id = r.rower_id
INNER JOIN logbook l ON r.logbook_id = l.id
WHERE u.id NOT IN (
    SELECT ur.user_id
    FROM user_role ur
    INNER JOIN role ro ON ur.role_id = ro.id
    WHERE ro.name = 'Donau Linz'
)
AND l.distance_in_km IS NOT NULL
AND l.arrival LIKE '{year}-%'
AND u.name != 'Externe Steuerperson';
"
        ))
        .fetch_one(db)
        .await
        .unwrap()
        .get::<i64, usize>(0) as i32;

        Stat {
            name: "Gäste".into(),
            rowed_km: rowed_km + rowed_km_guests,
        }
    }

    pub async fn sum_people(db: &SqlitePool, year: Option<i32>) -> i32 {
        let stats = Self::people(db, year).await;
        let mut sum = 0;
        for stat in stats {
            sum += stat.rowed_km;
        }

        sum
    }

    pub async fn people(db: &SqlitePool, year: Option<i32>) -> Vec<Stat> {
        let year = match year {
            Some(year) => year,
            None => chrono::Local::now().year(),
        };
        //TODO: switch to query! macro again (once upgraded to sqlite 3.42 on server)
        sqlx::query(&format!(
            "
SELECT u.name, CAST(SUM(l.distance_in_km) AS INTEGER) AS rowed_km 
FROM (
    SELECT * FROM user 
    WHERE id IN (
        SELECT user_id FROM user_role 
        JOIN role ON user_role.role_id = role.id 
        WHERE role.name = 'Donau Linz'
    )
) u
INNER JOIN rower r ON u.id = r.rower_id
INNER JOIN logbook l ON r.logbook_id = l.id
WHERE l.distance_in_km IS NOT NULL AND l.arrival LIKE '{year}-%' AND u.name != 'Externe Steuerperson'
GROUP BY u.name
ORDER BY rowed_km DESC, u.name;
"
        ))
        .fetch_all(db)
        .await
        .unwrap()
        .into_iter()
        .map(|row| Stat {
            name: row.get("name"),
            rowed_km: row.get("rowed_km"),
        })
        .collect()
    }
}

#[derive(Debug, Serialize)]
pub struct PersonalStat {
    date: String,
    km: i32,
}

pub async fn get_personal(db: &SqlitePool, user: &User) -> Vec<PersonalStat> {
    sqlx::query(&format!(
        "
SELECT 
    departure_date as date,
    SUM(total_distance) OVER (ORDER BY departure_date) as km 
FROM (
    SELECT 
        date(l.departure) as departure_date,
        COALESCE(SUM(l.distance_in_km),0) as total_distance
    FROM 
        logbook l
    LEFT JOIN 
        rower r ON l.id = r.logbook_id
    WHERE 
        l.shipmaster = {0} OR r.rower_id = {0}
    GROUP BY 
        departure_date
) as subquery
ORDER BY 
    departure_date;
",
        user.id
    ))
    .fetch_all(db)
    .await
    .unwrap()
    .into_iter()
    .map(|row| PersonalStat {
        date: row.get("date"),
        km: row.get("km"),
    })
    .collect()
}
