use crate::database::models::version_item;
use crate::database::redis::RedisPool;
use crate::file_hosting::FileHost;
use crate::models;
use crate::models::ids::ImageId;
use crate::models::projects::{Loader, Project, ProjectStatus};
use crate::models::v2::projects::{
    DonationLink, LegacyProject, LegacySideType,
};
use crate::queue::session::AuthQueue;
use crate::routes::v3::project_creation::default_project_type;
use crate::routes::v3::project_creation::{CreateError, NewGalleryItem};
use crate::routes::{v2_reroute, v3};
use actix_multipart::Multipart;
use actix_web::web::Data;
use actix_web::{post, HttpRequest, HttpResponse};
use serde::{Deserialize, Serialize};
use serde_json::json;
use sqlx::postgres::PgPool;

use std::collections::HashMap;
use std::sync::Arc;
use validator::Validate;

use super::version_creation::InitialVersionData;

pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
    cfg.service(project_create);
}

pub fn default_requested_status() -> ProjectStatus {
    ProjectStatus::Approved
}

#[derive(Serialize, Deserialize, Validate, Clone)]
struct ProjectCreateData {
    #[validate(
        length(min = 3, max = 64),
        custom(function = "crate::util::validate::validate_name")
    )]
    #[serde(alias = "mod_name")]
    /// The title or name of the project.
    pub title: String,
    #[validate(length(min = 1, max = 64))]
    #[serde(default = "default_project_type")]
    /// The project type of this mod
    pub project_type: String,
    #[validate(
        length(min = 3, max = 64),
        regex = "crate::util::validate::RE_URL_SAFE"
    )]
    #[serde(alias = "mod_slug")]
    /// The slug of a project, used for vanity URLs
    pub slug: String,
    #[validate(length(min = 3, max = 255))]
    #[serde(alias = "mod_description")]
    /// A short description of the project.
    pub description: String,
    #[validate(length(max = 65536))]
    #[serde(alias = "mod_body")]
    /// A long description of the project, in markdown.
    pub body: String,

    /// The support range for the client project
    pub client_side: LegacySideType,
    /// The support range for the server project
    pub server_side: LegacySideType,

    #[validate(length(max = 32))]
    #[validate]
    /// A list of initial versions to upload with the created project
    pub initial_versions: Vec<InitialVersionData>,
    #[validate(length(max = 3))]
    /// A list of the categories that the project is in.
    pub categories: Vec<String>,
    #[validate(length(max = 256))]
    #[serde(default = "Vec::new")]
    /// A list of the categories that the project is in.
    pub additional_categories: Vec<String>,

    #[validate(
        custom(function = "crate::util::validate::validate_url"),
        length(max = 2048)
    )]
    /// An optional link to where to submit bugs or issues with the project.
    pub issues_url: Option<String>,
    #[validate(
        custom(function = "crate::util::validate::validate_url"),
        length(max = 2048)
    )]
    /// An optional link to the source code for the project.
    pub source_url: Option<String>,
    #[validate(
        custom(function = "crate::util::validate::validate_url"),
        length(max = 2048)
    )]
    /// An optional link to the project's wiki page or other relevant information.
    pub wiki_url: Option<String>,
    #[validate(
        custom(function = "crate::util::validate::validate_url"),
        length(max = 2048)
    )]
    /// An optional link to the project's license page
    pub license_url: Option<String>,
    #[validate(
        custom(function = "crate::util::validate::validate_url"),
        length(max = 2048)
    )]
    /// An optional link to the project's discord.
    pub discord_url: Option<String>,
    /// An optional list of all donation links the project has\
    #[validate]
    pub donation_urls: Option<Vec<DonationLink>>,

    /// An optional boolean. If true, the project will be created as a draft.
    pub is_draft: Option<bool>,

    /// The license id that the project follows
    pub license_id: String,

    #[validate(length(max = 64))]
    #[validate]
    /// The multipart names of the gallery items to upload
    pub gallery_items: Option<Vec<NewGalleryItem>>,
    #[serde(default = "default_requested_status")]
    /// The status of the mod to be set once it is approved
    pub requested_status: ProjectStatus,

    // Associations to uploaded images in body/description
    #[validate(length(max = 10))]
    #[serde(default)]
    pub uploaded_images: Vec<ImageId>,

    /// The id of the organization to create the project in
    pub organization_id: Option<models::ids::OrganizationId>,
}

#[post("project")]
pub async fn project_create(
    req: HttpRequest,
    payload: Multipart,
    client: Data<PgPool>,
    redis: Data<RedisPool>,
    file_host: Data<Arc<dyn FileHost + Send + Sync>>,
    session_queue: Data<AuthQueue>,
) -> Result<HttpResponse, CreateError> {
    // Convert V2 multipart payload to V3 multipart payload
    let payload = v2_reroute::alter_actix_multipart(
        payload,
        req.headers().clone(),
        |legacy_create: ProjectCreateData, _| async move {
            // Side types will be applied to each version
            let client_side = legacy_create.client_side;
            let server_side = legacy_create.server_side;

            let project_type = legacy_create.project_type;

            let initial_versions = legacy_create
                .initial_versions
                .into_iter()
                .map(|v| {
                    let mut fields = HashMap::new();
                    fields.extend(v2_reroute::convert_side_types_v3(
                        client_side,
                        server_side,
                    ));
                    fields.insert(
                        "game_versions".to_string(),
                        json!(v.game_versions),
                    );

                    // Modpacks now use the "mrpack" loader, and loaders are converted to loader fields.
                    // Setting of 'project_type' directly is removed, it's loader-based now.
                    if project_type == "modpack" {
                        fields.insert(
                            "mrpack_loaders".to_string(),
                            json!(v.loaders),
                        );
                    }

                    let loaders = if project_type == "modpack" {
                        vec![Loader("mrpack".to_string())]
                    } else {
                        v.loaders
                    };

                    v3::version_creation::InitialVersionData {
                        project_id: v.project_id,
                        file_parts: v.file_parts,
                        version_number: v.version_number,
                        version_title: v.version_title,
                        version_body: v.version_body,
                        dependencies: v.dependencies,
                        release_channel: v.release_channel,
                        loaders,
                        featured: v.featured,
                        primary_file: v.primary_file,
                        status: v.status,
                        file_types: v.file_types,
                        uploaded_images: v.uploaded_images,
                        ordering: v.ordering,
                        fields,
                    }
                })
                .collect();

            let mut link_urls = HashMap::new();
            if let Some(issue_url) = legacy_create.issues_url {
                link_urls.insert("issues".to_string(), issue_url);
            }
            if let Some(source_url) = legacy_create.source_url {
                link_urls.insert("source".to_string(), source_url);
            }
            if let Some(wiki_url) = legacy_create.wiki_url {
                link_urls.insert("wiki".to_string(), wiki_url);
            }
            if let Some(discord_url) = legacy_create.discord_url {
                link_urls.insert("discord".to_string(), discord_url);
            }
            if let Some(donation_urls) = legacy_create.donation_urls {
                for donation_url in donation_urls {
                    link_urls.insert(donation_url.platform, donation_url.url);
                }
            }

            Ok(v3::project_creation::ProjectCreateData {
                name: legacy_create.title,
                slug: legacy_create.slug,
                summary: legacy_create.description, // Description becomes summary
                description: legacy_create.body,    // Body becomes description
                initial_versions,
                categories: legacy_create.categories,
                additional_categories: legacy_create.additional_categories,
                license_url: legacy_create.license_url,
                link_urls,
                is_draft: legacy_create.is_draft,
                license_id: legacy_create.license_id,
                gallery_items: legacy_create.gallery_items,
                requested_status: legacy_create.requested_status,
                uploaded_images: legacy_create.uploaded_images,
                organization_id: legacy_create.organization_id,
            })
        },
    )
    .await?;

    // Call V3 project creation
    let response = v3::project_creation::project_create(
        req,
        payload,
        client.clone(),
        redis.clone(),
        file_host,
        session_queue,
    )
    .await?;

    // Convert response to V2 format
    match v2_reroute::extract_ok_json::<Project>(response).await {
        Ok(project) => {
            let version_item = match project.versions.first() {
                Some(vid) => {
                    version_item::Version::get((*vid).into(), &**client, &redis)
                        .await?
                }
                None => None,
            };
            let project = LegacyProject::from(project, version_item);
            Ok(HttpResponse::Ok().json(project))
        }
        Err(response) => Ok(response),
    }
}
