use std::env;
use axum::{
    routing::{get, post},
    http::StatusCode,
    Json, Router,
};
use axum::extract::{Path, State};
use axum::routing::delete;
use dotenv::dotenv;
use env_logger::fmt::style::Reset;
use http::{header, Method};
use common::models::article::{delete_article, insert_article, load_article, load_articles, update_article, Article};
use common::models::origin::Origin;
use sqlx::{ postgres::PgPoolOptions, PgPool };
use tower_http::cors::{AllowOrigin, Any, CorsLayer};
use tracing::info;
use http::header::{AUTHORIZATION, ACCEPT, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE, ACCESS_CONTROL_ALLOW_CREDENTIALS};
use common::models::article;
use common::models::label::{delete_label, insert_label, load_labels, update_label, Label};
use common::models::settings::{load_settings, update_settings, Settings};
use common::models::supplier::{delete_supplier, insert_supplier, load_suppliers, update_supplier, Supplier};
use common::models::unit::Unit;
use common::models::variant::{delete_variant, insert_variant, update_variant, Variant};

#[tokio::main]
async fn main() {
    dotenv().ok();

    let server_address = env::var("SERVER_ADDRESS").expect("SERVER_ADDRESS is not set in .env file");
    let server_port = env::var("SERVER_PORT").expect("SERVER_PORT is not set in .env file");
    let database = env::var("DATABASE_URL").expect("DATABASE is not set in .env file");

    let addr = format!("{}:{}", server_address, server_port);

    // initialize tracing
    tracing_subscriber::fmt().init();

    let pool = PgPoolOptions::new()
        .max_connections(50)
        .connect(&database)
        .await
        .expect(&format!("Can't connect to database: {}", &database));

    //sqlx::migrate!().run(&pool).await.expect("Migrations failed");

    let cors = CorsLayer::new()
        .allow_methods([Method::GET, Method::POST, Method::DELETE])
        .allow_headers([ACCEPT, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE])
        .allow_origin(AllowOrigin::any());

    // build our application with a route
    let app = Router::new()
        .route("/articles", get(get_articles))
        .route("/article", post(create_article))
        .route("/article/{id}", get(get_article))
        .route("/article/{id}", post(update_article_route))
        .route("/article/{id}", delete(delete_article_route))
        .route("/article/{id}/variant", post(add_variant))
        .route("/variant/{id}", post(update_variant_route))
        .route("/variant/{id}", delete(delete_variant_route))
        .route("/label", get(get_labels_route))
        .route("/label", post(add_label_route))
        .route("/label/{id}", post(update_label_route))
        .route("/label/{id}", delete(delete_label_route))
        .route("/settings", get(get_settings_route))
        .route("/settings", post(update_settings_route))
        .route("/supplier", get(get_suppliers_route))
        .route("/supplier", post(add_supplier_route))
        .route("/supplier/{id}", post(update_supplier_route))
        .route("/supplier/{id}", delete(delete_supplier_route))

        .with_state(pool)
        .layer(cors);

    let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
    info!("Server started on: {}", &addr);

    axum::serve(listener, app).await.unwrap();
}

#[tracing::instrument(ret)]
async fn get_articles(
    State(pool): State<PgPool>,
) -> (StatusCode, Json<Vec<Article>>) {
    info!("Get all articles");

    let result_articles: Option<Vec<Article>> = load_articles(pool).await;

    match result_articles {
        Some(articles) => {
            (StatusCode::OK, Json(articles))
        }
        None => {
            (StatusCode::INTERNAL_SERVER_ERROR, Json(vec![]))
        }
    }
}

#[tracing::instrument(ret)]
async fn get_article(
    Path(id): Path<i64>,
    State(pool): State<PgPool>,
) -> (StatusCode, Json<Article>) {
    info!("Get article with id: {}", id);

    let result_article: Option<Article> = load_article(id, pool).await;

    match result_article {
        Some(article) => {
            (StatusCode::OK, Json(article))
        }
        None => {
            let article = Article {
                id,
                name: "".to_string(),
                bio: false,
                origin: Origin::EU,
                description: None,
                ingredients: None,
                default_unit: Unit::G,
                variants: vec![],
                mhd: None,
                bio_origin: None,
                allergens_text: None,
                bio_info: None,
                topm_id: None,
                additional_name: None,
            };

            (StatusCode::INTERNAL_SERVER_ERROR, Json(article))
        }
    }
}

#[tracing::instrument(ret)]
async fn create_article(
    State(pool): State<PgPool>,
    Json(payload): Json<Article>,
) -> (StatusCode, Json<Article>) {
    info!("Create article");

    match insert_article(&payload, pool).await {
        Some(article) => {
            (StatusCode::CREATED, Json(article))
        }
        None => {
            let article = Article {
                id: 0,
                name: "".to_string(),
                bio: false,
                origin: Origin::EU,
                description: None,
                ingredients: None,
                default_unit: Unit::G,
                variants: vec![],
                mhd: None,
                bio_origin: None,
                allergens_text: None,
                bio_info: None,
                topm_id: None,
                additional_name: None,
            };

            (StatusCode::INTERNAL_SERVER_ERROR, Json(article))
        }
    }
}

async fn update_article_route(
    State(pool): State<PgPool>,
    Path(article_id): Path<i64>,
    Json(payload): Json<Article>,
) -> (StatusCode, Json<Article>) {
    info!("Update article");

    let update_article = update_article(article_id, &payload, pool).await;

    match update_article {
        Some(article) => {
            (StatusCode::OK, Json(article))
        }
        None => {
            let article = Article {
                id: 0,
                name: "".to_string(),
                bio: false,
                origin: Origin::EU,
                description: None,
                ingredients: None,
                default_unit: Unit::G,
                variants: vec![],
                mhd: None,
                bio_origin: None,
                allergens_text: None,
                bio_info: None,
                topm_id: None,
                additional_name: None,
            };

            (StatusCode::INTERNAL_SERVER_ERROR, Json(article))
        }
    }
}

async fn delete_article_route(
    State(pool): State<PgPool>,
    Path(article_id): Path<i64>,
) -> StatusCode {
    info!("Delete article");

    let delete_article_result = delete_article(article_id, pool).await;

    match delete_article_result {
        Some(_) => {
            StatusCode::OK
        }
        None => {
            StatusCode::INTERNAL_SERVER_ERROR
        }
    }
}


async fn add_variant(
    State(pool): State<PgPool>,
    Path(article_id): Path<i64>,
    Json(payload): Json<Variant>,
) -> (StatusCode, Json<Variant>) {
    info!("Add variant");

    let created_variant = insert_variant(article_id, &payload, pool).await;

    match created_variant {
        Some(variant) => {
            (StatusCode::OK, Json(variant))
        }
        None => {
            let variant = Variant {
                id: 0,
                weight: "".to_string(),
                ean: None,
                name: None,
                description: None,
                ingredients: None,
                unit: Unit::G,
                label: None,
                variant_description: None,
                topm_row: None,
            };

            (StatusCode::INTERNAL_SERVER_ERROR, Json(variant))
        }
    }
}

async fn update_variant_route(
    State(pool): State<PgPool>,
    Path(variant_id): Path<i64>,
    Json(payload): Json<Variant>,
) -> (StatusCode, Json<Variant>) {
    info!("Update variant");

    let update_variant = update_variant(variant_id, &payload, pool).await;

    match update_variant {
        Some(variant) => {
            (StatusCode::OK, Json(variant))
        }
        None => {
            let variant = Variant {
                id: 0,
                weight: "".to_string(),
                ean: None,
                name: None,
                description: None,
                ingredients: None,
                unit: Unit::G,
                label: None,
                variant_description: None,
                topm_row: None,
            };

            (StatusCode::INTERNAL_SERVER_ERROR, Json(variant))
        }
    }
}

async fn delete_variant_route(
    State(pool): State<PgPool>,
    Path(variant_id): Path<i64>,
) -> StatusCode {
    info!("Delete variant");

    let delete_variant_result = delete_variant(variant_id, pool).await;

    match delete_variant_result {
        Some(_) => {
            StatusCode::OK
        }
        None => {
            StatusCode::INTERNAL_SERVER_ERROR
        }
    }
}

#[tracing::instrument(ret)]
async fn get_labels_route(
    State(pool): State<PgPool>,
) -> (StatusCode, Json<Vec<Label>>) {
    info!("Get all labels");

    match load_labels(pool).await {
        Some(labels) => {
            (StatusCode::OK, Json(labels))
        }
        None => {
            (StatusCode::INTERNAL_SERVER_ERROR, Json(vec![]))
        }
    }
}

#[tracing::instrument(ret)]
async fn add_label_route(
    State(pool): State<PgPool>,
    Json(payload): Json<Label>,
) -> (StatusCode, Json<Label>) {
    info!("Add label");

    match insert_label(&payload, pool).await {
        Some(variant) => {
            (StatusCode::OK, Json(variant))
        }
        None => {
            let label = Label {
                id: 0,
                name: "".to_string(),
            };

            (StatusCode::INTERNAL_SERVER_ERROR, Json(label))
        }
    }
}

#[tracing::instrument(ret)]
async fn update_label_route(
    State(pool): State<PgPool>,
    Path(label_id): Path<i64>,
    Json(payload): Json<Label>,
) -> (StatusCode, Json<Label>) {
    info!("Update label");

    match update_label(label_id, &payload, pool).await {
        Some(variant) => {
            (StatusCode::OK, Json(variant))
        }
        None => {
            let label = Label {
                id: 0,
                name: "".to_string(),
            };

            (StatusCode::INTERNAL_SERVER_ERROR, Json(label))
        }
    }
}

async fn delete_label_route(
    State(pool): State<PgPool>,
    Path(label_id): Path<i64>,
) -> StatusCode {
    info!("Delete label");

    let delete_variant_result = delete_label(label_id, pool).await;

    match delete_variant_result {
        Some(_) => {
            StatusCode::OK
        }
        None => {
            StatusCode::INTERNAL_SERVER_ERROR
        }
    }
}

#[tracing::instrument(ret)]
async fn get_settings_route(
    State(pool): State<PgPool>,
) -> (StatusCode, Json<Settings>) {
    info!("Get settings");

    match load_settings(&pool).await {
        Some(settings) => {
            (StatusCode::OK, Json(settings))
        }
        None => {
            (StatusCode::INTERNAL_SERVER_ERROR, Json(
                Settings {
                    id: 0,
                    mhd: "".to_string(),
                    bio: "".to_string(),
                    allergens_text: "".to_string(),
                    bio_info: "".to_string(),
                }
            ))
        }
    }
}

#[tracing::instrument(ret)]
async fn update_settings_route(
    State(pool): State<PgPool>,
    Json(payload): Json<Settings>,
) -> (StatusCode, Json<Settings>) {
    info!("Update settings");

    match update_settings(&payload, pool).await {
        Some(settings) => {
            (StatusCode::OK, Json(settings))
        }
        None => {
            (StatusCode::INTERNAL_SERVER_ERROR, Json(
                Settings {
                    id: 0,
                    mhd: "".to_string(),
                    bio: "".to_string(),
                    allergens_text: "".to_string(),
                    bio_info: "".to_string(),
                }
            ))
        }
    }
}

#[tracing::instrument(ret)]
async fn get_suppliers_route(
    State(pool): State<PgPool>,
) -> (StatusCode, Json<Vec<Supplier>>) {
    info!("Get all suppliers");

    match load_suppliers(pool).await {
        Some(suppliers) => {
            (StatusCode::OK, Json(suppliers))
        }
        None => {
            (StatusCode::INTERNAL_SERVER_ERROR, Json(vec![]))
        }
    }
}

#[tracing::instrument(ret)]
async fn add_supplier_route(
    State(pool): State<PgPool>,
    Json(payload): Json<Supplier>,
) -> (StatusCode, Json<Supplier>) {
    info!("Add supplier");

    match insert_supplier(&payload, pool).await {
        Some(supplier) => {
            (StatusCode::OK, Json(supplier))
        }
        None => {
            let supplier = Supplier {
                id: 0,
                topm_id: "".to_string(),
                name: "".to_string(),
                bio_info: None,
            };

            (StatusCode::INTERNAL_SERVER_ERROR, Json(supplier))
        }
    }
}

#[tracing::instrument(ret)]
async fn update_supplier_route(
    State(pool): State<PgPool>,
    Path(supplier_id): Path<i64>,
    Json(payload): Json<Supplier>,
) -> (StatusCode, Json<Supplier>) {
    info!("Update supplier");

    match update_supplier(supplier_id, &payload, pool).await {
        Some(supplier) => {
            (StatusCode::OK, Json(supplier))
        }
        None => {
            let supplier = Supplier {
                id: 0,
                topm_id: "".to_string(),
                name: "".to_string(),
                bio_info: None,
            };

            (StatusCode::INTERNAL_SERVER_ERROR, Json(supplier))
        }
    }
}

async fn delete_supplier_route(
    State(pool): State<PgPool>,
    Path(supplier_id): Path<i64>,
) -> StatusCode {
    info!("Delete supplier");

    let deleted_result = delete_supplier(supplier_id, pool).await;

    match deleted_result {
        Some(_) => {
            StatusCode::OK
        }
        None => {
            StatusCode::INTERNAL_SERVER_ERROR
        }
    }
}
