use std::collections::HashMap;

use askama::Template;
use axum::{
    extract::{Path, State},
    response::{Html, IntoResponse as _, Response},
};
use interim_models::{
    field::Field,
    field_form_prompt::FieldFormPrompt,
    language::Language,
    portal::Portal,
    presentation::{Presentation, TextInputMode},
};
use interim_pgtypes::{pg_attribute::PgAttribute, pg_class::PgClass};
use serde::Deserialize;
use uuid::Uuid;

use crate::{
    Settings,
    app::AppDbConn,
    errors::{AppError, not_found},
    field_info::FormFieldInfo,
    workspace_pooler::{RoleAssignment, WorkspacePooler},
};

#[derive(Debug, Deserialize)]
pub(super) struct PathParams {
    portal_id: Uuid,
}

pub(super) async fn get(
    State(settings): State<Settings>,
    State(mut pooler): State<WorkspacePooler>,
    AppDbConn(mut app_db): AppDbConn,
    Path(PathParams { portal_id }): Path<PathParams>,
) -> Result<Response, AppError> {
    let portal = Portal::with_id(portal_id)
        .fetch_optional(&mut app_db)
        .await?
        .ok_or(not_found!("form not found"))?;

    // FIXME: auth

    // WARNING: This client is connected with full workspace privileges. Even
    // more so than usual, the Phonograph server is responsible for ensuring all
    // auth checks are performed properly.
    //
    // TODO: Can this be delegated to a dedicated and less privileged role
    // instead?
    let mut workspace_client = pooler
        .acquire_for(portal.workspace_id, RoleAssignment::Root)
        .await?;

    let rel = PgClass::with_oid(portal.class_oid)
        .fetch_one(&mut workspace_client)
        .await?;
    let attrs: HashMap<String, PgAttribute> = PgAttribute::all_for_rel(portal.class_oid)
        .fetch_all(&mut workspace_client)
        .await?
        .into_iter()
        .map(|value| (value.attname.clone(), value))
        .collect();

    // TODO: implement with sql join
    let mut fields: Vec<FormFieldInfo> = vec![];
    for field in Field::belonging_to_portal(portal_id)
        .fetch_all(&mut app_db)
        .await?
    {
        let attr = attrs.get(&field.name);
        let prompts: HashMap<Language, String> = FieldFormPrompt::belonging_to_field(field.id)
            .fetch_all(&mut app_db)
            .await?
            .into_iter()
            .map(|prompt| (prompt.language, prompt.content))
            .collect();
        fields.push(FormFieldInfo {
            field,
            column_present: attr.is_some(),
            has_default: attr.is_some_and(|value| value.atthasdef),
            not_null: attr.is_some_and(|value| value.attnotnull.is_some_and(|notnull| notnull)),
            prompts,
        })
    }

    let mut prompts_html: HashMap<String, String> = HashMap::new();
    for field in fields.iter() {
        // TODO: i18n
        let prompt = field
            .prompts
            .get(&Language::Eng)
            .cloned()
            .unwrap_or_default();
        let prompt_md = markdown::to_html(&prompt);
        // TODO: a11y (input labels)
        prompts_html.insert(field.field.name.clone(), prompt_md);
    }

    #[derive(Debug, Template)]
    #[template(path = "forms/form_index.html")]
    struct ResponseTemplate {
        fields: Vec<FormFieldInfo>,
        language: Language,
        portal: Portal,
        prompts_html: HashMap<String, String>,
        settings: Settings,
    }

    Ok(Html(
        ResponseTemplate {
            fields,
            language: Language::Eng,
            portal,
            prompts_html,
            settings,
        }
        .render()?,
    )
    .into_response())
}
