use derive_builder::Builder;
use serde::{Deserialize, Serialize};
use sqlx::query_as;
use uuid::Uuid;

use crate::{client::AppDbClient, language::Language};

/// A localized prompt to display above or alongside the form input for the
/// given field.
///
/// There may be zero or one `field_form_prompt` entries for each
/// `(field_id, language)` pair. (This uniqueness should be enforced by the
/// database.)
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FieldFormPrompt {
    /// Primary key (defaults to UUIDv7).
    pub id: Uuid,

    /// ID of the field to which this prompt belongs.
    pub field_id: Uuid,

    /// [ISO 639-3](https://en.wikipedia.org/wiki/List_of_ISO_639-3_codes)
    /// language code.
    pub language: Language,

    /// Prompt content for this field, in this language.
    pub content: String,
}

impl FieldFormPrompt {
    /// Build an insert statement to create a new prompt.
    pub fn upsert() -> UpsertBuilder {
        UpsertBuilder::default()
    }

    /// Build an update statement to alter the content of an existing prompt.
    pub fn update() -> UpdateBuilder {
        UpdateBuilder::default()
    }

    /// Build a single-field query by field ID.
    pub fn belonging_to_field(id: Uuid) -> BelongingToFieldQuery {
        BelongingToFieldQuery { id }
    }
}

#[derive(Builder, Clone, Debug)]
pub struct Upsert {
    field_id: Uuid,
    language: Language,
    content: String,
}

impl Upsert {
    pub async fn execute(self, app_db: &mut AppDbClient) -> Result<FieldFormPrompt, sqlx::Error> {
        query_as!(
            FieldFormPrompt,
            r#"
insert into field_form_prompts (field_id, language, content) values ($1, $2, $3)
on conflict (field_id, language) do update set
    content = excluded.content
returning
    id,
    field_id,
    language as "language: Language",
    content
"#,
            self.field_id,
            self.language.to_string(),
            self.content,
        )
        .fetch_one(app_db.get_conn())
        .await
    }
}

#[derive(Builder, Clone, Debug, Default)]
pub struct Update {
    id: Uuid,
    content: String,
}

impl Update {
    pub async fn execute(self, app_db: &mut AppDbClient) -> Result<FieldFormPrompt, sqlx::Error> {
        query_as!(
            FieldFormPrompt,
            r#"
update field_form_prompts
set content = $1
where id = $2
returning
    id,
    field_id,
    language as "language: Language",
    content
"#,
            self.content,
            self.id,
        )
        .fetch_one(app_db.get_conn())
        .await
    }
}

#[derive(Clone, Debug)]
pub struct BelongingToFieldQuery {
    id: Uuid,
}

impl BelongingToFieldQuery {
    pub async fn fetch_all(
        self,
        app_db: &mut AppDbClient,
    ) -> Result<Vec<FieldFormPrompt>, sqlx::Error> {
        query_as!(
            FieldFormPrompt,
            r#"
select
    id,
    field_id,
    language as "language: Language",
    content
from field_form_prompts
where field_id = $1
"#,
            self.id,
        )
        .fetch_all(app_db.get_conn())
        .await
    }
}
