use hyper::header::CONTENT_TYPE;
use hyper::{Body, Response};
use std::collections::HashMap;
use tera::Context;

use crate::controller::utils;
use crate::controller::wallet::Wallet;
use crate::db;
use crate::model::frequency::Frequency;
use crate::queries;
use crate::templates;
use crate::validation;

static PER_PAGE: i64 = 10;

pub async fn table(
    wallet: &Wallet,
    query: queries::Payments,
) -> Response<Body> {
    let page = query.page.unwrap_or(1);
    let count = db::payments::count(&wallet.pool, &query).await;
    let payments =
        db::payments::list_for_table(&wallet.pool, &query, PER_PAGE).await;
    let max_page = (count.count as f32 / PER_PAGE as f32).ceil() as i64;
    let users = db::users::list(&wallet.pool).await;
    let categories = db::categories::list(&wallet.pool).await;

    let mut context = Context::new();
    context.insert("header", &templates::Header::Payments);
    context.insert("connected_user", &wallet.user);
    context.insert("payments", &payments);
    context.insert("page", &page);
    context.insert("max_page", &max_page);
    context.insert("query", &query);
    context.insert("count", &count.count);
    context.insert("total_cost", &count.total_cost);
    context.insert("users", &users);
    context.insert("categories", &categories);

    utils::template(
        &wallet.assets,
        &wallet.templates,
        "payment/table.html",
        context,
    )
}

pub async fn create_form(
    wallet: &Wallet,
    query: queries::Payments,
) -> Response<Body> {
    create_form_feedback(wallet, query, HashMap::new(), None).await
}

async fn create_form_feedback(
    wallet: &Wallet,
    query: queries::Payments,
    form: HashMap<String, String>,
    error: Option<String>,
) -> Response<Body> {
    let users = db::users::list(&wallet.pool).await;
    let categories = db::categories::list(&wallet.pool).await;

    let mut context = Context::new();
    context.insert("header", &templates::Header::Payments);
    context.insert("connected_user", &wallet.user);
    context.insert("users", &users);
    context.insert("categories", &categories);
    context.insert("query", &query);
    context.insert("form", &form);
    context.insert("error", &error);

    utils::template(
        &wallet.assets,
        &wallet.templates,
        "payment/create.html",
        context,
    )
}

pub async fn create(
    wallet: &Wallet,
    query: queries::Payments,
    form: HashMap<String, String>,
) -> Response<Body> {
    let error = |e: &str| {
        create_form_feedback(wallet, query, form.clone(), Some(e.to_string()))
    };

    match validation::payment::create(&form) {
        Some(create_payment) => {
            match db::payments::create(&wallet.pool, &create_payment).await {
                Some(id) => {
                    let row = db::payments::get_row(
                        &wallet.pool,
                        id,
                        create_payment.frequency,
                    )
                    .await;
                    let page = (row - 1) / PER_PAGE + 1;
                    let query = queries::Payments {
                        page: Some(page),
                        name: None,
                        cost: None,
                        frequency: Some(create_payment.frequency),
                        highlight: Some(id),
                        user: None,
                        category: None,
                        start_date: None,
                        end_date: None,
                    };
                    utils::redirect(&format!(
                        "/{}",
                        queries::payments_url(query)
                    ))
                }
                None => error("Erreur serveur.").await,
            }
        }
        None => error("Erreur lors de la validation du formulaire.").await,
    }
}

pub async fn update_form(
    id: i64,
    wallet: &Wallet,
    query: queries::Payments,
) -> Response<Body> {
    update_form_feedback(id, wallet, query, HashMap::new(), None).await
}

async fn update_form_feedback(
    id: i64,
    wallet: &Wallet,
    query: queries::Payments,
    form: HashMap<String, String>,
    error: Option<String>,
) -> Response<Body> {
    let payment = db::payments::get_for_form(&wallet.pool, id).await;
    let users = db::users::list(&wallet.pool).await;
    let categories = db::categories::list(&wallet.pool).await;

    let mut context = Context::new();
    context.insert("header", &templates::Header::Payments);
    context.insert("connected_user", &wallet.user);
    context.insert("id", &id);
    context.insert("payment", &payment);
    context.insert("users", &users);
    context.insert("categories", &categories);
    context.insert("query", &query);
    context.insert("form", &form);
    context.insert("error", &error);

    utils::template(
        &wallet.assets,
        &wallet.templates,
        "payment/update.html",
        context,
    )
}

pub async fn update(
    id: i64,
    wallet: &Wallet,
    query: queries::Payments,
    form: HashMap<String, String>,
) -> Response<Body> {
    let error = |e: &str| {
        update_form_feedback(
            id,
            wallet,
            query.clone(),
            form.clone(),
            Some(e.to_string()),
        )
    };

    match validation::payment::update(&form) {
        Some(update_payment) => {
            if db::payments::update(&wallet.pool, id, &update_payment).await {
                let frequency = query.frequency.unwrap_or(Frequency::Punctual);
                let row =
                    db::payments::get_row(&wallet.pool, id, frequency).await;
                let page = (row - 1) / PER_PAGE + 1;
                // TODO: keep name, cost, user and category when updating a line
                let query = queries::Payments {
                    page: Some(page),
                    name: None,
                    cost: None,
                    frequency: Some(frequency),
                    highlight: Some(id),
                    user: None,
                    category: None,
                    start_date: None,
                    end_date: None,
                };
                utils::redirect(&format!("/{}", queries::payments_url(query)))
            } else {
                error("Erreur serveur").await
            }
        }
        None => error("Erreur lors de la validation du formulaire.").await,
    }
}

pub async fn delete(
    id: i64,
    wallet: &Wallet,
    query: queries::Payments,
) -> Response<Body> {
    if db::payments::delete(&wallet.pool, id).await {
        let query = queries::Payments {
            highlight: None,
            ..query
        };
        utils::redirect(&format!("/{}", queries::payments_url(query)))
    } else {
        update_form_feedback(
            id,
            wallet,
            query,
            HashMap::new(),
            Some("Erreur serveur".to_string()),
        )
        .await
    }
}

pub async fn search_category(
    wallet: &Wallet,
    query: queries::PaymentCategory,
) -> Response<Body> {
    match db::payments::search_category(&wallet.pool, query.payment_name).await
    {
        Some(category_id) => utils::with_header(
            Response::new(format!("{}", category_id).into()),
            CONTENT_TYPE,
            "application/json",
        ),
        None => utils::not_found(),
    }
}