use std::collections::HashMap;

use diesel::{prelude::*, PgConnection};
use uuid::Uuid;

use crate::models::{Document, DocumentAsset, DocumentAssetObject, DocumentVersion};
use crate::schema::{document_asset_objects, document_assets, document_versions, documents};
use crate::state::AppState;

pub(crate) struct DocumentVersionContext {
    pub document: Document,
    pub version: DocumentVersion,
    pub tenant_id: Uuid,
}

pub(crate) fn load_document_version(
    state: &AppState,
    tenant_id: Uuid,
    document_id: Uuid,
    version_id: Uuid,
) -> Result<DocumentVersionContext, String> {
    let mut conn = state
        .db_for_tenant(tenant_id)
        .map_err(|err| format!("{err:?}"))?;

    let version: DocumentVersion = document_versions::table
        .find(version_id)
        .first(&mut conn)
        .map_err(|err| format!("{err:?}"))?;

    if version.document_id != document_id {
        return Err("document/version mismatch".into());
    }

    let document: Document = documents::table
        .find(document_id)
        .first(&mut conn)
        .map_err(|err| format!("{err:?}"))?;

    Ok(DocumentVersionContext {
        document,
        version,
        tenant_id,
    })
}

pub(crate) struct LoadedAsset {
    pub asset: DocumentAsset,
    pub objects: Vec<DocumentAssetObject>,
}

pub(crate) fn load_version_assets(
    conn: &mut PgConnection,
    tenant_id: Uuid,
    version_id: Uuid,
    asset_types: &[&str],
) -> Result<HashMap<String, LoadedAsset>, String> {
    let mut query = document_assets::table
        .filter(document_assets::document_version_id.eq(version_id))
        .filter(document_assets::tenant_id.eq(tenant_id))
        .into_boxed();

    if !asset_types.is_empty() {
        let types: Vec<String> = asset_types.iter().map(|ty| (*ty).to_string()).collect();
        query = query.filter(document_assets::asset_type.eq_any(types));
    }

    let assets: Vec<DocumentAsset> = query
        .order(document_assets::created_at.asc())
        .load(conn)
        .map_err(|err| format!("{err:?}"))?;

    let asset_ids: Vec<Uuid> = assets.iter().map(|asset| asset.id).collect();

    let mut object_map: HashMap<Uuid, Vec<DocumentAssetObject>> = HashMap::new();
    if !asset_ids.is_empty() {
        let objects: Vec<DocumentAssetObject> = document_asset_objects::table
            .filter(document_asset_objects::asset_id.eq_any(&asset_ids))
            .filter(document_asset_objects::tenant_id.eq(tenant_id))
            .order(document_asset_objects::ordinal.asc())
            .load(conn)
            .map_err(|err| format!("{err:?}"))?;

        for object in objects {
            object_map.entry(object.asset_id).or_default().push(object);
        }
    }

    let mut result = HashMap::with_capacity(assets.len());
    for asset in assets {
        let objects = object_map.remove(&asset.id).unwrap_or_default();
        result.insert(asset.asset_type.clone(), LoadedAsset { asset, objects });
    }

    Ok(result)
}
