#![allow(clippy::missing_errors_doc)]
#![allow(clippy::unnecessary_struct_initialization)]
#![allow(clippy::unused_async)]
use axum::http::HeaderMap;
use loco_rs::prelude::*;
use sea_orm::{ColumnTrait, QueryFilter, QueryOrder};
use serde::{Deserialize, Serialize};

use crate::controllers::admin::check_auth;
use crate::models::_entities::friend_links::{ActiveModel, Column, Entity, Model};
use crate::services::{admin_audit, notifications};

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Params {
    pub site_name: Option<String>,
    pub site_url: String,
    pub avatar_url: Option<String>,
    pub description: Option<String>,
    pub category: Option<String>,
    pub status: Option<String>,
}

impl Params {
    fn update(&self, item: &mut ActiveModel) {
        item.site_url = Set(self.site_url.clone());
        if let Some(site_name) = &self.site_name {
            item.site_name = Set(Some(site_name.clone()));
        }
        if let Some(avatar_url) = &self.avatar_url {
            item.avatar_url = Set(Some(avatar_url.clone()));
        }
        if let Some(description) = &self.description {
            item.description = Set(Some(description.clone()));
        }
        if let Some(category) = &self.category {
            item.category = Set(Some(category.clone()));
        }
        if let Some(status) = &self.status {
            item.status = Set(Some(status.clone()));
        }
    }
}

#[derive(Clone, Debug, Default, Deserialize)]
pub struct ListQuery {
    pub status: Option<String>,
    pub category: Option<String>,
}

#[derive(Clone, Debug, Deserialize)]
pub struct CreateFriendLinkRequest {
    #[serde(default, alias = "siteName")]
    pub site_name: Option<String>,
    #[serde(alias = "siteUrl")]
    pub site_url: String,
    #[serde(default, alias = "avatarUrl")]
    pub avatar_url: Option<String>,
    #[serde(default)]
    pub description: Option<String>,
    #[serde(default)]
    pub category: Option<String>,
    #[serde(default)]
    pub status: Option<String>,
}

async fn load_item(ctx: &AppContext, id: i32) -> Result<Model> {
    let item = Entity::find_by_id(id).one(&ctx.db).await?;
    item.ok_or_else(|| Error::NotFound)
}

#[debug_handler]
pub async fn list(
    Query(query): Query<ListQuery>,
    State(ctx): State<AppContext>,
    headers: HeaderMap,
) -> Result<Response> {
    let authenticated = check_auth(&headers).ok();
    let mut db_query = Entity::find().order_by_desc(Column::CreatedAt);

    if let Some(status) = query.status {
        db_query = db_query.filter(Column::Status.eq(status));
    } else if authenticated.is_none() {
        db_query = db_query.filter(Column::Status.eq("approved"));
    }

    if let Some(category) = query.category {
        db_query = db_query.filter(Column::Category.eq(category));
    }

    format::json(db_query.all(&ctx.db).await?)
}

#[debug_handler]
pub async fn add(
    State(ctx): State<AppContext>,
    Json(params): Json<CreateFriendLinkRequest>,
) -> Result<Response> {
    let mut item = ActiveModel {
        ..Default::default()
    };
    item.site_name = Set(params.site_name);
    item.site_url = Set(params.site_url);
    item.avatar_url = Set(params.avatar_url);
    item.description = Set(params.description);
    item.category = Set(params.category);
    item.status = Set(Some(params.status.unwrap_or_else(|| "pending".to_string())));
    let item = item.insert(&ctx.db).await?;
    notifications::notify_new_friend_link(&ctx, &item).await;
    format::json(item)
}

#[debug_handler]
pub async fn update(
    headers: HeaderMap,
    Path(id): Path<i32>,
    State(ctx): State<AppContext>,
    Json(params): Json<Params>,
) -> Result<Response> {
    let actor = check_auth(&headers)?;
    let item = load_item(&ctx, id).await?;
    let mut item = item.into_active_model();
    params.update(&mut item);
    let item = item.update(&ctx.db).await?;
    admin_audit::log_event(
        &ctx,
        Some(&actor),
        "friend_link.update",
        "friend_link",
        Some(item.id.to_string()),
        item.site_name
            .clone()
            .or_else(|| Some(item.site_url.clone())),
        Some(serde_json::json!({ "status": item.status })),
    )
    .await?;
    format::json(item)
}

#[debug_handler]
pub async fn remove(
    headers: HeaderMap,
    Path(id): Path<i32>,
    State(ctx): State<AppContext>,
) -> Result<Response> {
    let actor = check_auth(&headers)?;
    let item = load_item(&ctx, id).await?;
    let label = item
        .site_name
        .clone()
        .or_else(|| Some(item.site_url.clone()));
    item.delete(&ctx.db).await?;
    admin_audit::log_event(
        &ctx,
        Some(&actor),
        "friend_link.delete",
        "friend_link",
        Some(id.to_string()),
        label,
        None,
    )
    .await?;
    format::empty()
}

#[debug_handler]
pub async fn get_one(
    headers: HeaderMap,
    Path(id): Path<i32>,
    State(ctx): State<AppContext>,
) -> Result<Response> {
    check_auth(&headers)?;
    format::json(load_item(&ctx, id).await?)
}

pub fn routes() -> Routes {
    Routes::new()
        .prefix("api/friend_links/")
        .add("/", get(list))
        .add("/", post(add))
        .add("{id}", get(get_one))
        .add("{id}", delete(remove))
        .add("{id}", put(update))
        .add("{id}", patch(update))
}
