use reqwest::Client;
use rocket::http::Status;
use rocket::serde::json::Json;

use crate::json_result::JsonResult;
use crate::model::person::PersonCreate;
use crate::model::reniec_person::ReniecPerson;
use crate::{db, model::person::Person};

#[get("/person/<dni>")]
pub async fn get_by_dni(dni: i32) -> (Status, Json<JsonResult<Person>>) {
    let db = db();

    // TODO: move db logic to model
    /*
     * Search person in DB
     */
    let query = sqlx::query!("SELECT * FROM person WHERE person_dni = ?", dni)
        .fetch_one(db)
        .await;

    // Return if found
    if let Ok(row) = query {
        let person = Person {
            person_id: row.person_id,
            person_dni: row.person_dni,
            person_names: row.person_names,
            person_paternal_surname: row.person_paternal_surname,
            person_maternal_surname: row.person_maternal_surname,
            person_classroom_id: None,
        };

        return (Status::Ok, JsonResult::ok(person));
    }

    /*
     * Person not found in DB. Search in fake RENIEC API with reqwest
     */
    let reniec_api_key = std::env::var("RENIEC_API").expect("RENIEC_API env var is not set!");
    let client = Client::new();
    let reqwest_result = client
        .get(format!("https://api.apis.net.pe/v1/dni?numero={}", &dni))
        .bearer_auth(reniec_api_key)
        .header("Content-Type", "application/json")
        .header("charset", "utf-8")
        .send()
        .await;

    let reniec_person = match reqwest_result {
        Ok(data) => {
            let person = data.json::<ReniecPerson>().await;
            match person {
                Ok(p) => Some(p),
                Err(reason) => {
                    eprintln!(
                        "Error parsing data from fake RENIEC API into ReniecPerson: {:?}",
                        reason
                    );
                    None
                }
            }
        }
        Err(error) => {
            eprintln!("Error fetching person from fake RENIEC API: {:?}", error);
            None
        }
    };

    // If person is found in fake RENIEC API, insert to DB and return
    if let Some(p) = reniec_person {
        // Insert person into DB and get last inserted id
        let mut tx = match db.begin().await {
            Ok(t) => t,
            Err(err) => {
                eprintln!("Error starting transaction: {:?}", err);
                return (
                    Status::InternalServerError,
                    JsonResult::err(format!("Error iniciando transaccion.")),
                );
            }
        };

        let query_1 = sqlx::query!(
            "INSERT INTO person (person_dni, person_names, person_paternal_surname, person_maternal_surname) VALUES (?, ?, ?, ?)",
            dni,
            p.nombres,
            p.apellidoPaterno,
            p.apellidoMaterno,
        )
        .execute(&mut *tx)
        .await;

        let query_2 = sqlx::query!("SELECT LAST_INSERT_ID() AS person_id")
            .fetch_one(&mut *tx)
            .await;

        match tx.commit().await {
            Ok(_) => (),
            Err(err) => {
                eprintln!("Error commiting transaction: {:?}", err);
                return (
                    Status::InternalServerError,
                    JsonResult::err(format!("Error confirmando transaccion.")),
                );
            }
        }

        if let Err(reason) = query_1 {
            eprintln!("Error inserting person into DB: {:?}", reason);
            return (
                Status::InternalServerError,
                JsonResult::err(format!("Error insertando a DB.")),
            );
        }

        let inserted_id = match query_2 {
            Ok(value) => value.person_id,
            Err(error) => {
                println!("Error getting last inserted id: {:?}", error);
                return (
                    Status::InternalServerError,
                    JsonResult::err(format!("Error recuperando ID insertado.")),
                );
            }
        };

        return (
            Status::Ok,
            JsonResult::ok(Person {
                person_id: inserted_id as i32,
                person_dni: format!("{}", dni),
                person_names: p.nombres,
                person_paternal_surname: p.apellidoPaterno,
                person_maternal_surname: p.apellidoMaterno,
                person_classroom_id: None,
            }),
        );
    }

    // Return error
    (
        Status::NotFound,
        JsonResult::err(format!("Persona no encontrada.")),
    )
}

#[options("/person")]
pub fn options() -> Status {
    Status::Ok
}

#[post("/person", format = "json", data = "<person>")]
pub async fn create_person(person: Json<PersonCreate>) -> Status {
    match person.create().await {
        Ok(_) => Status::Created,
        Err(reason) => {
            eprintln!("Error creating person: {:?}", reason);

            Status::InternalServerError
        }
    }
}
