//! `PostgresModuleSource` — the Postgres-backed `ModuleSource` impl.
//!
//! `resolve` is **lexical** (Phase 4b): a module is looked up by walking the
//! group chain rooted at the **importing script's defining node** (`origin`),
//! nearest-owner-wins — *not* the inheriting app's effective view. An `App`
//! origin walks `app → its group → ancestors` (reusing [`CHAIN_LEVELS_CTE`]);
//! a `Group` origin walks `group → ancestors` ([`GROUP_CHAIN_LEVELS_CTE`]) and
//! never sees app-owned modules below it (the trust boundary).
//!
//! `resolve_policy` adds §5.5 **extension points**: the nearest declaration of
//! a name on `origin`'s chain decides lexical (concrete module) vs **dynamic**
//! (an extension-point marker → resolve against the inheriting app so it can
//! override, falling back to a default body up-chain). EP wins a depth tie.
//!
//! The resolver lives in `executor-core` and consumes this trait through the
//! `Services` bundle, so manager-core stays the only crate that touches Postgres.

use async_trait::async_trait;
use chrono::{DateTime, Utc};
use picloud_shared::{
    AppId, ModuleResolution, ModuleScript, ModuleSource, ModuleSourceError, ScriptOwner,
};
use sqlx::PgPool;

use crate::config_resolver::{CHAIN_LEVELS_CTE, GROUP_CHAIN_LEVELS_CTE};

pub struct PostgresModuleSource {
    pool: PgPool,
}

impl PostgresModuleSource {
    #[must_use]
    pub fn new(pool: PgPool) -> Self {
        Self { pool }
    }
}

#[derive(sqlx::FromRow)]
struct ModuleRow {
    id: uuid::Uuid,
    app_id: Option<uuid::Uuid>,
    group_id: Option<uuid::Uuid>,
    name: String,
    source: String,
    updated_at: DateTime<Utc>,
}

impl From<ModuleRow> for ModuleScript {
    fn from(r: ModuleRow) -> Self {
        Self {
            script_id: r.id.into(),
            app_id: r.app_id.map(Into::into),
            group_id: r.group_id.map(Into::into),
            name: r.name,
            source: r.source,
            updated_at: r.updated_at,
        }
    }
}

#[async_trait]
impl ModuleSource for PostgresModuleSource {
    async fn resolve(
        &self,
        origin: ScriptOwner,
        name: &str,
    ) -> Result<Option<ModuleScript>, ModuleSourceError> {
        // Lexical lookup: JOIN module scripts against the chain rooted at
        // `origin`, take the nearest level (lowest depth). The `kind =
        // 'module'` filter + the CHECK constraint guarantee endpoint
        // scripts are never importable. Case-insensitive to match the
        // per-owner `LOWER(name)` partial-unique indexes (0050). `origin`
        // is a trusted dispatch-derived value, so the chain it roots is
        // the script's true ancestry — the cross-app isolation boundary
        // is preserved (a group origin can't reach an app's modules).
        let query = match origin {
            ScriptOwner::App(_) => format!(
                "{CHAIN_LEVELS_CTE} \
                 SELECT s.id, s.app_id, s.group_id, s.name, s.source, s.updated_at \
                 FROM scripts s \
                 JOIN chain c ON (s.app_id = c.app_owner OR s.group_id = c.group_owner) \
                 WHERE s.kind = 'module' AND LOWER(s.name) = LOWER($2) \
                 ORDER BY c.depth ASC LIMIT 1",
            ),
            ScriptOwner::Group(_) => format!(
                "{GROUP_CHAIN_LEVELS_CTE} \
                 SELECT s.id, s.app_id, s.group_id, s.name, s.source, s.updated_at \
                 FROM scripts s \
                 JOIN chain c ON s.group_id = c.group_owner \
                 WHERE s.kind = 'module' AND LOWER(s.name) = LOWER($2) \
                 ORDER BY c.depth ASC LIMIT 1",
            ),
        };
        let root = match origin {
            ScriptOwner::App(a) => a.into_inner(),
            ScriptOwner::Group(g) => g.into_inner(),
        };
        let row: Option<ModuleRow> = sqlx::query_as(&query)
            .bind(root)
            .bind(name)
            .fetch_optional(&self.pool)
            .await
            .map_err(|e| ModuleSourceError::Backend(e.to_string()))?;
        Ok(row.map(Into::into))
    }

    async fn resolve_policy(
        &self,
        origin: ScriptOwner,
        inheriting_app: AppId,
        name: &str,
    ) -> Result<ModuleResolution, ModuleSourceError> {
        // §5.5 nearest-declaration-kind-wins. One round trip: the min chain
        // depth at which `name` is declared as an extension-point marker vs a
        // concrete module, walking up `origin`'s chain. The EP wins a tie (a
        // marker + its co-located default body live at the same node).
        let query = match origin {
            ScriptOwner::App(_) => format!(
                "{CHAIN_LEVELS_CTE} \
                 SELECT \
                   (SELECT MIN(c.depth) FROM extension_points e \
                      JOIN chain c ON (e.app_id = c.app_owner OR e.group_id = c.group_owner) \
                      WHERE LOWER(e.name) = LOWER($2)) AS ep_depth, \
                   (SELECT MIN(c.depth) FROM scripts s \
                      JOIN chain c ON (s.app_id = c.app_owner OR s.group_id = c.group_owner) \
                      WHERE s.kind = 'module' AND LOWER(s.name) = LOWER($2)) AS mod_depth",
            ),
            ScriptOwner::Group(_) => format!(
                "{GROUP_CHAIN_LEVELS_CTE} \
                 SELECT \
                   (SELECT MIN(c.depth) FROM extension_points e \
                      JOIN chain c ON e.group_id = c.group_owner \
                      WHERE LOWER(e.name) = LOWER($2)) AS ep_depth, \
                   (SELECT MIN(c.depth) FROM scripts s \
                      JOIN chain c ON s.group_id = c.group_owner \
                      WHERE s.kind = 'module' AND LOWER(s.name) = LOWER($2)) AS mod_depth",
            ),
        };
        let root = match origin {
            ScriptOwner::App(a) => a.into_inner(),
            ScriptOwner::Group(g) => g.into_inner(),
        };
        let (ep_depth, mod_depth): (Option<i32>, Option<i32>) = sqlx::query_as(&query)
            .bind(root)
            .bind(name)
            .fetch_one(&self.pool)
            .await
            .map_err(|e| ModuleSourceError::Backend(e.to_string()))?;

        // EP wins when it is declared and is no farther than the nearest
        // concrete module (tie → EP). Then resolve dynamically against the
        // inheriting app (its own override, else the default body up-chain).
        let ep_wins = match (ep_depth, mod_depth) {
            (Some(ep), Some(m)) => ep <= m,
            (Some(_), None) => true,
            _ => false,
        };
        if ep_wins {
            return Ok(
                match self.resolve(ScriptOwner::App(inheriting_app), name).await? {
                    Some(m) => ModuleResolution::Module(m),
                    None => ModuleResolution::NoProvider,
                },
            );
        }
        if mod_depth.is_some() {
            // Concrete module nearest → lexical, exactly as Phase 4b.
            return Ok(match self.resolve(origin, name).await? {
                Some(m) => ModuleResolution::Module(m),
                None => ModuleResolution::NotFound,
            });
        }
        Ok(ModuleResolution::NotFound)
    }
}
