use argon2::Params;
use axum::{
    extract::{
        Path, State,
        ws::{Message, close_code::STATUS},
    },
    http::StatusCode,
    response::IntoResponse,
};
use rusqlite::params;
use serde::Serialize;
use serde_json::json;

use crate::utils::{auth::AuthClaims, db_pool::AppState};

pub async fn create_inventory_item(
    State(state): State<AppState>,
    Path((item_name, item_amount)): Path<(String, i32)>,
    AuthClaims { user_id, hotel_id }: AuthClaims,
) -> impl IntoResponse {
    let pool = state.hotel_pools.get_pool(hotel_id);

    let conn = match pool.get() {
        Ok(conn) => conn,
        Err(err) => {
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("couldn't open the connection"),
            );
        }
    };

    let result = conn.execute(
        "INSERT INTO inventory (item_name, amount, user_id) VALUES (?1, ?2, ?3)",
        params![&item_name, &item_amount, &user_id],
    );
    match result {
        Ok(rows) => (
            StatusCode::OK,
            format!("inserted item {item_name}, with {item_amount} amount"),
        ),
        Err(err) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            format!("couldn't add the new item, err: {}", err),
        ),
    }
}

#[derive(Serialize)]
pub struct InventoryItems {
    id: i32,
    amount: i32,
    name: String,
    user_id: i32,
    updated_at: String,
}

pub async fn update_inventory_item(
    State(state): State<AppState>,
    Path((item_id, item_amount)): Path<(i32, i32)>,
    AuthClaims { user_id, hotel_id }: AuthClaims,
) -> impl IntoResponse {

    let pool = state.hotel_pools.get_pool(hotel_id);

    let conn = match pool.get() {
        Ok(conn) => conn,
        Err(err) => {
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Pool error: {err}"),
            );
        }
    };

    let result: Result<String, rusqlite::Error> = conn.query_row(
        "UPDATE inventory SET amount = ?1, user_id=?3 WHERE id = ?2 RETURNING item_name",
        params![&item_amount, &item_id, &user_id],
        |row| row.get(0),
    );

    match result {
        Ok(item_name) => {
            if let Err(err) = conn.execute(
                "INSERT INTO inventory_history (item_id, amount, item_name, user_id) VALUES (?1,?2,?3,?4)",
                params![&item_id,&item_amount,&item_name, &user_id]
            ){
                return (StatusCode::INTERNAL_SERVER_ERROR, format!("failed to update inventory history"));
            }

            if let Some(hotel_users) = state.ws_map.get(&hotel_id) {
                let update_msg = json!({
                    "event_type": "item_update",
                    "item_id": item_id,
                    "number": item_amount,
                    "updated_by": user_id,
                })
                .to_string();

                for entry in hotel_users.iter() {
                    let sender = entry.value();
                    // ignore errors (client disconnected)
                    let _ = sender.send(Message::Text(update_msg.clone().into()));
                }
            }

            (StatusCode::OK, format!("updated item history"))
        }

        Ok(_) => (
            StatusCode::NOT_FOUND,
            "No item found, err : {_}".to_string(),
        ),
        Err(err) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            format!("Error from DB: {err}"),
        ),
    }
}

pub async fn get_inventory_item(
    State(state): State<AppState>,
    AuthClaims { user_id, hotel_id }: AuthClaims,
) -> impl IntoResponse {
    let pool = state.hotel_pools.get_pool(hotel_id);

    let conn = match pool.get() {
        Ok(c) => c,
        Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Pool error".to_string()),
    };

    let mut stmt =
        match conn.prepare("SELECT id, amount,  item_name, user_id, updated_at FROM inventory") {
            Ok(s) => s,
            Err(_) => {
                return (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    "Statement error".to_string(),
                );
            }
        };

    let mut query_result = match stmt.query([]) {
        Ok(r) => r,
        Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Query error".to_string()),
    };

    let mut items = Vec::new();

    while let Ok(Some(row)) = query_result.next() {
        let item = InventoryItems {
            id: row.get("id").unwrap_or_default(),
            amount: row.get("amount").unwrap_or_default(),
            name: row.get("item_name").unwrap_or_default(),
            user_id: row.get("user_id").unwrap_or_default(),
            updated_at: row.get("updated_at").unwrap_or_default(),
        };
        items.push(item);
    }

    let json = match serde_json::to_string(&items) {
        Ok(j) => j,
        Err(_) => {
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                "Serialization error".to_string(),
            );
        }
    };

    (StatusCode::OK, json)
}
