use rocket::http::Status;

use rocket::serde::json::Json;

use crate::DbConn;
use crate::dns::{RecordConnector, ZoneConnector};
use crate::models;
use crate::models::{ParseRecordList};
use crate::controllers;


#[get("/zones/<zone>/records")]
pub async fn get_zone_records(
    mut dns_api: Box<dyn RecordConnector>,
    conn: DbConn,
    user_info: Result<models::UserInfo, models::ErrorResponse>,
    zone: models::AbsoluteName
) -> Result<Json<models::RecordList>, models::ErrorResponse> {

    let user_info = user_info?;
    let zone_name = zone.to_string();

    conn.run(move |c| {
        if user_info.is_admin() {
            models::Zone::get_by_name(c, &zone_name)
        } else {
            user_info.get_zone(c, &zone_name)
        }
    }).await?;

    let dns_records = dns_api.get_records(zone.clone(), models::DNSClass::IN.into()).await?;
    let records: Vec<_> = dns_records.into_iter().map(models::Record::from).collect();

    Ok(Json(records))
}

#[post("/zones/<zone>/records", data = "<new_records>")]
pub async fn create_zone_records(
    mut dns_api: Box<dyn RecordConnector>,
    conn: DbConn,
    user_info: Result<models::UserInfo, models::ErrorResponse>,
    zone: models::AbsoluteName,
    new_records: Json<models::RecordList>
) -> Result<Json<()>, models::ErrorResponse> {

    let user_info = user_info?;
    let zone_name = zone.to_utf8();

    conn.run(move |c| {
        if user_info.is_admin() {
            models::Zone::get_by_name(c, &zone_name)
        } else {
            user_info.get_zone(c, &zone_name)
        }
    }).await?;

    dns_api.add_records(
        zone.clone(),
        models::DNSClass::IN.into(),
        new_records.into_inner().try_into_dns_type(zone.into_inner(), models::DNSClass::IN.into())?
    ).await?;

    return Ok(Json(()));
}

#[put("/zones/<zone>/records", data = "<update_records_request>")]
pub async fn update_zone_records(
    mut dns_api: Box<dyn RecordConnector>,
    conn: DbConn,
    user_info: Result<models::UserInfo, models::ErrorResponse>,
    zone: models::AbsoluteName,
    update_records_request: Json<models::UpdateRecordsRequest>
) -> Result<Json<()>, models::ErrorResponse> {

    let user_info = user_info?;
    let zone = zone.into_inner();
    let zone_name = zone.to_utf8();
    let update_records_request = update_records_request.into_inner();

    conn.run(move |c| {
        if user_info.is_admin() {
            models::Zone::get_by_name(c, &zone_name)
        } else {
            user_info.get_zone(c, &zone_name)
        }
    }).await?;

    dns_api.update_records(
        zone.clone(),
        models::DNSClass::IN.into(),
        update_records_request.old_records.try_into_dns_type(zone.clone(), models::DNSClass::IN.into())?,
        update_records_request.new_records.try_into_dns_type(zone, models::DNSClass::IN.into())?,
    ).await?;

    return Ok(Json(()));
}

#[delete("/zones/<zone>/records", data = "<records>")]
pub async fn delete_zone_records(
    mut dns_api: Box<dyn RecordConnector>,
    conn: DbConn,
    user_info: Result<models::UserInfo, models::ErrorResponse>,
    zone: models::AbsoluteName,
    records: Json<models::RecordList>
) -> Result<Json<()>, models::ErrorResponse> {

    let user_info = user_info?;
    let zone_name = zone.to_utf8();

    conn.run(move |c| {
        if user_info.is_admin() {
            models::Zone::get_by_name(c, &zone_name)
        } else {
            user_info.get_zone(c, &zone_name)
        }
    }).await?;

    dns_api.delete_records(
        zone.clone(),
        models::DNSClass::IN.into(),
        records.into_inner().try_into_dns_type(zone.into_inner(), models::DNSClass::IN.into())?
    ).await?;

    return Ok(Json(()));
}

#[get("/zones")]
pub async fn get_zones(
    conn: DbConn,
    user_info: Result<models::UserInfo, models::ErrorResponse>,
) -> Result<Json<Vec<models::Zone>>, models::ErrorResponse> {
    let user_info = user_info?;

    controllers::get_zones(
        &conn,
        user_info
    ).await.map(|zones| Json(zones))
}

#[post("/zones", data = "<zone_request>")]
pub async fn create_zone(
    conn: DbConn,
    dns_api: Box<dyn ZoneConnector>,
    user_info: Result<models::UserInfo, models::ErrorResponse>,
    zone_request: Json<models::CreateZoneRequest>,
) -> Result<Json<models::Zone>, models::ErrorResponse> {
    let user_info = user_info?;

    controllers::create_zone(
        &conn,
        dns_api,
        user_info,
        zone_request.into_inner()
    ).await.map(|zone| Json(zone))
}


#[post("/zones/<zone>/members", data = "<zone_member_request>")]
pub async fn add_member_to_zone<'r>(
    conn: DbConn,
    zone: models::AbsoluteName,
    user_info: Result<models::UserInfo, models::ErrorResponse>,
    zone_member_request: Json<models::AddZoneMemberRequest>
) -> Result<Status, models::ErrorResponse> {
    let user_info = user_info?;
    let zone_name = zone.to_utf8();

    conn.run(move |c| {
        let zone = if user_info.is_admin() {
            models::Zone::get_by_name(c, &zone_name)
        } else {
            user_info.get_zone(c, &zone_name)
        }?;

        let new_member = models::LocalUser::get_user_by_uuid(c, &zone_member_request.id)?;
        zone.add_member(&c, &new_member)
    }).await?;

    Ok(Status::Created) // TODO: change this?
}