use actix_web::{Error, post, get, web, App, HttpResponse, HttpServer, Responder, HttpRequest};
use actix_cors::Cors;
use actix_files::Files;
use dotenv::dotenv;
use env_logger::{Builder, Env};
use sqlx::postgres::PgPoolOptions;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::collections::HashMap;
use bigdecimal::BigDecimal;
use chrono::{TimeZone, DateTime, Utc, NaiveDateTime};
use sqlx::FromRow;
use actix_web::http::header;
use sqlx::Row;
use bigdecimal::FromPrimitive;
use std::convert::Infallible;

mod auth;

#[derive(Deserialize)]
struct RegistrationData {
    email: String,
    haslo: String,
    imie: String,
    #[serde(rename = "confirmPassword")]
    confirm_password: String,
}

#[derive(Deserialize)]
struct LoginData {
    email: String,
    haslo: String,
}

#[derive(Serialize)]
struct LoginResponse {
    token: String,
    imie: String,
}

#[derive(sqlx::FromRow, serde::Serialize)]
struct Book {
    id: i32,
    tytul: String,
    autor: String,
    cena: BigDecimal,
    obraz_url: Option<String>,
    opis: Option<String>,
}

#[derive(Deserialize)]
struct CartItem {
    book_id: i32,
    quantity: i32,
}

#[derive(Serialize)]
struct OrderWithItems {
    id: i32,
    data_zamowienia: NaiveDateTime,
    suma_totalna: BigDecimal,
    status: Option<String>,
    items: Vec<OrderItem>,
}

#[derive(sqlx::FromRow, Serialize)]
struct OrderItem {
    tytul: String,
    autor: String,
    ilosc: i32,
    cena: BigDecimal,
}

#[derive(Deserialize)]
struct CheckoutRequest {
    items: Vec<CartItem>,
    total: f64,
}

async fn validate_token(token: Option<&str>) -> Result<i32, actix_web::Error> {
    let raw_token = token.ok_or(actix_web::error::ErrorUnauthorized("Unauthorized"))?;
    
    // Usuń prefiks "Bearer "
    let token = raw_token.trim_start_matches("Bearer ").trim();
    
    if token.starts_with("user-") {
        let user_id = token.replace("user-", "").replace("-token", "").parse()
            .map_err(|_| actix_web::error::ErrorUnauthorized("Invalid token"))?;
        Ok(user_id)
    } else {
        Err(actix_web::error::ErrorUnauthorized("Unauthorized"))
    }
}

#[post("/rejestracja")]
async fn rejestracja(
    form: web::Json<RegistrationData>,
    pool: web::Data<sqlx::PgPool>,
) -> impl Responder {
    // Walidacja hasła
    if form.haslo.len() < 8 {
        return HttpResponse::BadRequest().body("Hasło musi mieć minimum 8 znaków");
    }

    // Sprawdzenie, czy hasła się zgadzają
    if form.haslo != form.confirm_password {
        return HttpResponse::BadRequest().body("Hasła nie są identyczne");
    }

    // Hashowanie hasła
    let hashed_password = match bcrypt::hash(&form.haslo, bcrypt::DEFAULT_COST) {
        Ok(h) => h,
        Err(_) => return HttpResponse::InternalServerError().finish(),
    };

    // Zapisz do bazy danych
    match sqlx::query!(
        r#"
        INSERT INTO uzytkownicy (email, haslo, imie)
        VALUES ($1, $2, $3)
        "#,
        form.email,
        hashed_password,
        form.imie
    )
    .execute(pool.get_ref())
    .await
    {
        Ok(_) => HttpResponse::Created().body("Konto utworzone pomyślnie"),
        Err(e) => {
            if e.to_string().contains("duplicate key value") {
                HttpResponse::Conflict().body("Email jest już zarejestrowany")
            } else {
                HttpResponse::InternalServerError().finish()
            }
        }
    }
}

#[post("/login")]
async fn login(
    form: web::Json<LoginData>,
    pool: web::Data<sqlx::PgPool>,
) -> impl Responder {
    let user = match sqlx::query!(
        "SELECT id, haslo, imie FROM uzytkownicy WHERE email = $1",
        form.email
    )
    .fetch_optional(pool.get_ref())
    .await
    {
        Ok(Some(u)) => u,
        Ok(None) => return HttpResponse::Unauthorized().body("Nieprawidłowe dane"),
        Err(_) => return HttpResponse::InternalServerError().finish(),
    };

    match bcrypt::verify(&form.haslo, &user.haslo) {
        Ok(true) => {
            // W praktyce użyj JWT lub innego mechanizmu autentykacji
            let dummy_token = format!("user-{}-token", user.id);
            HttpResponse::Ok().json(LoginResponse {
                token: dummy_token,
                imie: user.imie,
            })
        },
        _ => HttpResponse::Unauthorized().body("Nieprawidłowe hasło"),
    }
}

#[get("/api/ksiazki")]
async fn get_ksiazki(
    pool: web::Data<sqlx::PgPool>,
    web::Query(params): web::Query<HashMap<String, String>>,
) -> impl Responder {
    let search_term = params.get("search").map(|s| s.as_str()).unwrap_or("");
    let sort_by = params.get("sort").map(|s| s.as_str()).unwrap_or("default");

    // Poprawione zapytanie bazowe
    let mut base_query = "SELECT
        id,
        tytul,
        autor,
        cena,
        COALESCE('/images/' || obraz_url, '/images/placeholder.jpg') as obraz_url,
        COALESCE(opis, 'Brak opisu') as opis
    FROM ksiazki".to_string();

    // Warunek WHERE
    let where_clause = if !search_term.is_empty() {
        " WHERE LOWER(tytul) LIKE LOWER($1) OR LOWER(autor) LIKE LOWER($1)"
    } else {
        ""
    };

    // Poprawna kolejność klauzul
    let order_clause = match sort_by {
        "price_asc" => " ORDER BY cena ASC",
        "price_desc" => " ORDER BY cena DESC",
        "title_asc" => " ORDER BY tytul ASC",
        "author_asc" => " ORDER BY autor ASC",
        _ => " ORDER BY tytul ASC"  // Domyślne sortowanie
    };

    // Łączymy części zapytania w odpowiedniej kolejności
    let query = format!("{}{}{}", base_query, where_clause, order_clause);

    let mut query_builder = sqlx::query_as::<_, Book>(&query);

    if !search_term.is_empty() {
        query_builder = query_builder.bind(format!("%{}%", search_term));
    }

    match query_builder.fetch_all(pool.get_ref()).await {
        Ok(books) => HttpResponse::Ok().json(books),
        Err(e) => {
            log::error!("Błąd bazy danych: {:?}", e);
            HttpResponse::InternalServerError().json(json!({"error": "Błąd serwera"}))
        }
    }
}

#[get("/api/ksiazki/{id}")]
async fn get_ksiazka(
    pool: web::Data<sqlx::PgPool>,
    path: web::Path<i32>,
) -> impl Responder {
    let id = path.into_inner();

    match sqlx::query_as!(
        Book,
        r#"
        SELECT
            id,
            tytul,
            autor,
            cena,
            COALESCE('/images/' || obraz_url, '/images/placeholder.jpg') as obraz_url,
            COALESCE(opis, 'Brak opisu') as opis
        FROM ksiazki
        WHERE id = $1
        "#,
        id
    )
    .fetch_optional(pool.get_ref())
    .await
    {
        Ok(Some(book)) => HttpResponse::Ok().json(book),
        Ok(None) => HttpResponse::NotFound().json(json!({"error": "Książka nie znaleziona"})),
        Err(e) => {
            log::error!("Błąd bazy danych: {}", e);
            HttpResponse::InternalServerError().json(json!({"error": "Błąd serwera"}))
        }
    }
}

#[derive(Serialize)]
struct UserInfo {
    id: i32,
    imie: String,
}

#[get("/api/check-auth")]
async fn check_auth(
    req: HttpRequest,
    pool: web::Data<sqlx::PgPool>, // Dodajemy pool jako parametr
) -> impl Responder {
    let token = req.headers().get("Authorization")
        .and_then(|h| h.to_str().ok());

    match validate_token(token).await {
        Ok(user_id) => {
            match sqlx::query!(
                "SELECT imie FROM uzytkownicy WHERE id = $1",
                user_id
            )
            .fetch_one(pool.get_ref()) // Używamy pool z parametru
            .await {
                Ok(u) => HttpResponse::Ok().json(json!({
                    "authenticated": true,
                    "user": {
                        "id": user_id,
                        "imie": u.imie
                    }
                })),
                Err(_) => HttpResponse::Ok().json(json!({
                    "authenticated": false,
                    "user": null
                }))
            }
        },
        Err(_) => HttpResponse::Ok().json(json!({
            "authenticated": false,
            "user": null
        }))
    }
}

#[derive(serde::Serialize)]
struct CartItemResponse {
    book_id: i32,
    quantity: i32,
    tytul: String,
    cena: BigDecimal,
    #[serde(rename = "obraz_url")]
    obraz_url: String,  // Zmiana z Option<String> na String
}

#[get("/api/cart")]
async fn get_cart(
    req: HttpRequest,
    pool: web::Data<sqlx::PgPool>,
) -> Result<HttpResponse, Error> {
    let user_id = validate_token(get_token(&req)).await?;

    let cart_items = sqlx::query_as!(
    CartItemResponse,
	    r#"SELECT 
		k.book_id as "book_id!",
		k.quantity as "quantity!",
		b.tytul as "tytul!",
		b.cena as "cena!",
		COALESCE('/images/' || NULLIF(b.obraz_url, ''), '/images/placeholder.jpg') as "obraz_url!"
	    FROM koszyk k
	    JOIN ksiazki b ON k.book_id = b.id
	    WHERE k.user_id = $1
            ORDER BY b.tytul"#,
	    user_id
	)
    .fetch_all(pool.get_ref())
    .await
    .map_err(|e| actix_web::error::ErrorInternalServerError(e))?;

    Ok(HttpResponse::Ok().json(cart_items))
}

fn get_token(req: &HttpRequest) -> Option<&str> {
    req.headers()
        .get("Authorization")
        .and_then(|h| h.to_str().ok())
}

#[post("/api/add-to-cart")]
async fn add_to_cart(
    cart_item: web::Json<CartItem>,
    req: HttpRequest,
    pool: web::Data<sqlx::PgPool>,
) -> Result<HttpResponse, actix_web::Error> {
    let token = req.headers().get("Authorization")
        .and_then(|h| h.to_str().ok());

    let user_id = validate_token(token).await?;

    sqlx::query!(
        "INSERT INTO koszyk (user_id, book_id, quantity)
        VALUES ($1, $2, $3)
        ON CONFLICT (user_id, book_id)
        DO UPDATE SET quantity = koszyk.quantity + $3",
        user_id,
        cart_item.book_id,
        cart_item.quantity
    )
    .execute(pool.get_ref())
    .await
    .map_err(|e| actix_web::error::ErrorInternalServerError(e))?;

    Ok(HttpResponse::Ok().json(json!({"status": "success"})))
}

#[get("/api/order-history")]
async fn get_order_history(
    req: HttpRequest,
    pool: web::Data<sqlx::PgPool>,
) -> Result<HttpResponse, Error> {
    let user_id = validate_token(get_token(&req)).await?;

    let orders = sqlx::query!(
        r#"
        SELECT 
            z.id as "id!",
            z.data_zamowienia as "data_zamowienia!",
            z.suma_totalna as "suma_totalna!",
            z.status,
            pz.ilosc as "ilosc!",
            pz.cena as "item_price!",
            k.tytul as "tytul!",
            k.autor as "autor!"
        FROM zamowienia z
        JOIN pozycje_zamowienia pz ON z.id = pz.zamowienie_id
        JOIN ksiazki k ON pz.book_id = k.id
        WHERE z.user_id = $1
        ORDER BY z.id DESC, k.tytul ASC
        "#,
        user_id
    )
    .fetch_all(pool.get_ref())
    .await
    .map_err(|e| actix_web::error::ErrorInternalServerError(e))?;

    let mut grouped_orders = HashMap::new();
	for record in orders {
	    let entry = grouped_orders.entry(record.id).or_insert(OrderWithItems {
		id: record.id,
		data_zamowienia: record.data_zamowienia,
		suma_totalna: record.suma_totalna,
		status: record.status,
		items: Vec::new(),
	    });
	    
	    entry.items.push(OrderItem {
		tytul: record.tytul,
		autor: record.autor,
		ilosc: record.ilosc,
		cena: record.item_price,
	    });
	}

    let result: Vec<OrderWithItems> = grouped_orders.into_values().collect();
    Ok(HttpResponse::Ok().json(result))
}

#[post("/api/checkout")]
async fn checkout(
    req: HttpRequest,
    pool: web::Data<sqlx::PgPool>,
    data: web::Json<CheckoutRequest>,
) -> Result<HttpResponse, actix_web::Error> {
    let user_id = validate_token(get_token(&req)).await?;

    let mut transaction = pool.begin().await
        .map_err(|e| actix_web::error::ErrorInternalServerError(e))?;

    // 1. Utwórz zamówienie
    let order_id = sqlx::query!(
        "INSERT INTO zamowienia (user_id, suma_totalna)
        VALUES ($1, $2) RETURNING id",
        user_id,
        BigDecimal::from_f64(data.total).ok_or_else(|| 
            actix_web::error::ErrorBadRequest("Invalid total value"))?
    )
    .fetch_one(&mut *transaction)
    .await
    .map_err(|e| actix_web::error::ErrorInternalServerError(e))?
    .id;

    // 2. Dodaj pozycje zamówienia
    for item in &data.items {
        let book = sqlx::query!(
            "SELECT cena FROM ksiazki WHERE id = $1",
            item.book_id
        )
        .fetch_one(&mut *transaction)
        .await
        .map_err(|e| actix_web::error::ErrorInternalServerError(e))?;

        sqlx::query!(
            "INSERT INTO pozycje_zamowienia (zamowienie_id, book_id, ilosc, cena)
            VALUES ($1, $2, $3, $4)",
            order_id,
            item.book_id,
            item.quantity,
            book.cena
        )
        .execute(&mut *transaction)
        .await
        .map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
    }

    // 3. Wyczyść koszyk
    sqlx::query!(
        "DELETE FROM koszyk WHERE user_id = $1",
        user_id
    )
    .execute(&mut *transaction)
    .await
    .map_err(|e| actix_web::error::ErrorInternalServerError(e))?;

    transaction.commit().await
        .map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
    
    Ok(HttpResponse::Ok().json(json!({"status": "success"})))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // Inicjalizacja loggera
    Builder::from_env(Env::default().default_filter_or("debug")).init();

    // Ładowanie zmiennych środowiskowych
    dotenv().ok();
    let database_url = std::env::var("DATABASE_URL")
        .expect("DATABASE_URL must be set in .env");

    // Utwórz pulę połączeń
    let pool = PgPoolOptions::new()
        .max_connections(5)
        .connect(&database_url)
        .await
        .expect("Failed to create pool");

    HttpServer::new(move || {
        let cors = Cors::default()
            .allow_any_origin()
            .allowed_methods(vec!["GET", "POST"])
            .allowed_headers(vec![
                header::CONTENT_TYPE,
                header::AUTHORIZATION,
                header::ACCEPT,
                header::HeaderName::from_static("content-type"),
            ])
            .supports_credentials();

        App::new()
            .app_data(web::Data::new(pool.clone()))
            .wrap(cors)
            .wrap(actix_web::middleware::Logger::default())
            .service(get_ksiazki)
            .service(get_ksiazka)
            .service(rejestracja)
            .service(login)
            .service(get_cart)
            .service(add_to_cart) // Dodaj
    	    .service(checkout)    // Dodaj
            .service(check_auth)
            .service(get_order_history)
            .service(
                Files::new("/images", "./static/images")
                    .show_files_listing(),
            )
            .service(Files::new("/", "./static").index_file("index.html"))
    })
    .bind("0.0.0.0:7999")?
    .run()
    .await
}