//! §M5.5 / audit 2026-06-11 H-D1: a MATERIALIZED group email-template copy
//! decrypts its inbound secret under the recovered GROUP AAD.
//!
//! This pins M3's novel path: a group email template seals its inbound secret v1
//! with AAD bound to the GROUP; the materializer copies the sealed bytes +
//! version verbatim into a per-app copy (app_id set, `materialized_from` = the
//! template); at inbound time `email_inbound_target` must recover the sealing
//! group via `materialized_from` so `open_email` opens the group-sealed bytes.
//! Sealing under the app instead (this copy's own owner) would fail the GCM tag.
//!
//! Skips cleanly when `DATABASE_URL` is unset.

#![allow(clippy::too_many_lines)]

use picloud_manager_core::secrets_service::{open_email, seal_email, SecretOwner};
use picloud_manager_core::trigger_repo::{PostgresTriggerRepo, TriggerRepo};
use picloud_shared::{GroupId, MasterKey};
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;
use uuid::Uuid;

async fn pool_or_skip() -> Option<PgPool> {
    let Ok(url) = std::env::var("DATABASE_URL") else {
        eprintln!("email_secret_aad: DATABASE_URL unset — skipping");
        return None;
    };
    let pool = PgPoolOptions::new()
        .max_connections(2)
        .connect(&url)
        .await
        .expect("connect");
    sqlx::migrate!("./migrations")
        .run(&pool)
        .await
        .expect("migrate");
    Some(pool)
}

fn uniq(p: &str) -> String {
    format!("{p}-{}", &Uuid::new_v4().to_string()[..8])
}

#[tokio::test]
async fn materialized_email_copy_decrypts_under_the_group_aad() {
    let Some(pool) = pool_or_skip().await else {
        return;
    };
    let key = MasterKey::from_bytes([7u8; 32]);

    // A group + a descendant app + a group-owned handler script.
    let (gid,): (Uuid,) =
        sqlx::query_as("INSERT INTO groups (slug, name) VALUES ($1, $1) RETURNING id")
            .bind(uniq("esa-g"))
            .fetch_one(&pool)
            .await
            .unwrap();
    let (app_id,): (Uuid,) =
        sqlx::query_as("INSERT INTO apps (slug, name, group_id) VALUES ($1, $1, $2) RETURNING id")
            .bind(uniq("esa-a"))
            .bind(gid)
            .fetch_one(&pool)
            .await
            .unwrap();
    let (admin_id,): (Uuid,) = sqlx::query_as(
        "INSERT INTO admin_users (username, password_hash) VALUES ($1, 'x') RETURNING id",
    )
    .bind(uniq("esa-u"))
    .fetch_one(&pool)
    .await
    .unwrap();
    let (script_id,): (Uuid,) = sqlx::query_as(
        "INSERT INTO scripts (group_id, name, source) \
         VALUES ($1, $2, 'log::info(\"x\")') RETURNING id",
    )
    .bind(gid)
    .bind(uniq("inbound"))
    .fetch_one(&pool)
    .await
    .unwrap();

    // Seal the inbound secret v1 under the GROUP (the shared-group-secret model),
    // and insert the group EMAIL TEMPLATE (group_id set, app_id NULL).
    let secret = serde_json::Value::String("group-hmac-key".into());
    let (ct, nonce, version) =
        seal_email(&key, SecretOwner::Group(GroupId::from(gid)), &secret, 65536).unwrap();
    assert_eq!(version, 1);
    let (tmpl_id,): (Uuid,) = sqlx::query_as(
        "INSERT INTO triggers (group_id, script_id, kind, enabled, dispatch_mode, \
            retry_max_attempts, retry_backoff, retry_base_ms, registered_by_principal) \
         VALUES ($1, $2, 'email', TRUE, 'async', 3, 'exponential', 1000, $3) RETURNING id",
    )
    .bind(gid)
    .bind(script_id)
    .bind(admin_id)
    .fetch_one(&pool)
    .await
    .unwrap();
    sqlx::query(
        "INSERT INTO email_trigger_details \
            (trigger_id, inbound_secret_encrypted, inbound_secret_nonce, inbound_secret_version) \
         VALUES ($1, $2, $3, $4)",
    )
    .bind(tmpl_id)
    .bind(&ct)
    .bind(nonce.to_vec())
    .bind(version)
    .execute(&pool)
    .await
    .unwrap();

    // Materialize a per-app copy: app-owned, `materialized_from` = the template,
    // sealed bytes + version copied VERBATIM (no reseal — the AAD is the group's).
    let (copy_id,): (Uuid,) = sqlx::query_as(
        "INSERT INTO triggers (app_id, script_id, kind, enabled, dispatch_mode, \
            retry_max_attempts, retry_backoff, retry_base_ms, registered_by_principal, \
            materialized_from) \
         VALUES ($1, $2, 'email', TRUE, 'async', 3, 'exponential', 1000, $3, $4) RETURNING id",
    )
    .bind(app_id)
    .bind(script_id)
    .bind(admin_id)
    .bind(tmpl_id)
    .fetch_one(&pool)
    .await
    .unwrap();
    sqlx::query(
        "INSERT INTO email_trigger_details \
            (trigger_id, inbound_secret_encrypted, inbound_secret_nonce, inbound_secret_version) \
         SELECT $1, inbound_secret_encrypted, inbound_secret_nonce, inbound_secret_version \
         FROM email_trigger_details WHERE trigger_id = $2",
    )
    .bind(copy_id)
    .bind(tmpl_id)
    .execute(&pool)
    .await
    .unwrap();

    // The inbound target for the COPY recovers the sealing group + version, and
    // the group-sealed bytes decrypt under Group(gid) — the exact path the
    // webhook receiver takes.
    let repo = PostgresTriggerRepo::new(pool.clone());
    let target = repo
        .email_inbound_target(copy_id.into())
        .await
        .unwrap()
        .expect("materialized copy is a valid inbound target");
    assert_eq!(
        target.sealing_group.map(GroupId::into_inner),
        Some(gid),
        "the copy recovers its template's group as the sealing owner"
    );
    assert_eq!(target.inbound_secret_version, 1);
    let owner = SecretOwner::Group(target.sealing_group.unwrap());
    let recovered = open_email(
        &key,
        owner,
        target.inbound_secret_encrypted.as_ref().unwrap(),
        target.inbound_secret_nonce.as_ref().unwrap(),
        target.inbound_secret_version,
    )
    .expect("group-sealed bytes decrypt under the recovered group AAD");
    assert_eq!(recovered, secret);

    // Opening under the COPY's OWN app owner (the wrong owner) must fail — proof
    // the AAD binds to the group, not the per-app row.
    assert!(
        open_email(
            &key,
            SecretOwner::App(app_id.into()),
            target.inbound_secret_encrypted.as_ref().unwrap(),
            target.inbound_secret_nonce.as_ref().unwrap(),
            target.inbound_secret_version,
        )
        .is_err(),
        "the group-sealed secret must NOT open under the copy's app owner"
    );

    // Cleanup (triggers/details/scripts cascade on group+app delete).
    let _ = sqlx::query("DELETE FROM apps WHERE id = $1")
        .bind(app_id)
        .execute(&pool)
        .await;
    let _ = sqlx::query("DELETE FROM groups WHERE id = $1")
        .bind(gid)
        .execute(&pool)
        .await;
    let _ = sqlx::query("DELETE FROM admin_users WHERE id = $1")
        .bind(admin_id)
        .execute(&pool)
        .await;
}
