use log::{error, info};
use reqwest::Client;
use rocket::http::Status;
use rocket::serde::json::Json;

use crate::json_result::JsonResult;
use crate::model::person::{PersonCreate, PersonLink};
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();
    info!("get person with dni {}", dni);

    /*
     * Search person in DB
     */
    match Person::get_by_dni(dni).await {
        Ok(person) => {
            return (Status::Ok, JsonResult::ok(person));
        }
        Err(error) => {
            match error {
                // Only if the person is not found in DB, continue
                sqlx::Error::RowNotFound => (),
                // Otherwise, throw an error
                _ => {
                    error!("Error searching person with dni {}: {:?}", dni, error);
                    return (
                        Status::InternalServerError,
                        JsonResult::err(format!("Error buscando persona en DB.")),
                    );
                }
            }
        }
    };

    info!("person with dni {} not found in db, search in RENIEC", dni);

    /*
     * 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) => {
            info!("person with dni {} found in RENIEC", dni);
            let person = data.json::<ReniecPerson>().await;
            match person {
                Ok(p) => Some(p),
                Err(reason) => {
                    error!(
                        "Error parsing data from fake RENIEC API into ReniecPerson: {:?}",
                        reason
                    );
                    None
                }
            }
        }
        Err(err) => {
            error!("Error fetching person from fake RENIEC API: {:?}", err);
            None
        }
    };

    // If person is found in fake RENIEC API, insert to DB and return
    if let Some(p) = reniec_person {
        info!("Inserting person found in RENIEC into DB");

        // Insert person into DB and get last inserted id
        let mut tx = match db.begin().await {
            Ok(t) => t,
            Err(err) => {
                error!("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) => {
                error!("Error commiting transaction: {:?}", err);
                return (
                    Status::InternalServerError,
                    JsonResult::err(format!("Error confirmando transaccion.")),
                );
            }
        }

        if let Err(reason) = query_1 {
            error!("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) => {
                error!("Error getting last inserted id: {:?}", error);
                return (
                    Status::InternalServerError,
                    JsonResult::err(format!("Error recuperando ID insertado.")),
                );
            }
        };

        info!("person inserted into db, returning");
        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,
                person_classroom_username: None,
            }),
        );
    }

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

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

#[put("/person/link", format = "json", data = "<data>")]
pub async fn link_person(data: Json<PersonLink>) -> (Status, Json<JsonResult<()>>) {
    match data.0.insert().await {
        Ok(_) => (Status::Ok, JsonResult::ok(())),
        Err(reason) => (Status::Ok, JsonResult::err(reason)),
    }
}

#[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
        }
    }
}
