use super::*;

impl PostgresRegistry {
    pub async fn list_secrets(
        &self,
        workspace_id: &WorkspaceId,
    ) -> Result<Vec<SecretRecord>, RegistryError> {
        let rows = sqlx::query!(
            "select
                id,
                workspace_id,
                name,
                kind,
                status,
                current_version,
                created_at as \"created_at!: time::OffsetDateTime\",
                updated_at as \"updated_at!: time::OffsetDateTime\",
                last_used_at as \"last_used_at: time::OffsetDateTime\"
             from secrets
             where workspace_id = $1
             order by name asc",
            workspace_id.as_str(),
        )
        .fetch_all(&self.pool)
        .await?;

        rows.into_iter()
            .map(|row| {
                Ok(SecretRecord {
                    secret: Secret {
                        id: SecretId::new(row.id),
                        workspace_id: WorkspaceId::new(row.workspace_id),
                        name: row.name,
                        kind: deserialize_enum_text(&row.kind, "kind")?,
                        status: deserialize_enum_text(&row.status, "status")?,
                        current_version: from_db_version(row.current_version, "current_version")?,
                        created_at: row.created_at,
                        updated_at: row.updated_at,
                        last_used_at: row.last_used_at,
                    },
                })
            })
            .collect()
    }

    pub async fn get_secret(
        &self,
        workspace_id: &WorkspaceId,
        secret_id: &SecretId,
    ) -> Result<Option<SecretRecord>, RegistryError> {
        let row = sqlx::query!(
            "select
                id,
                workspace_id,
                name,
                kind,
                status,
                current_version,
                created_at as \"created_at!: time::OffsetDateTime\",
                updated_at as \"updated_at!: time::OffsetDateTime\",
                last_used_at as \"last_used_at: time::OffsetDateTime\"
             from secrets
             where workspace_id = $1 and id = $2",
            workspace_id.as_str(),
            secret_id.as_str(),
        )
        .fetch_optional(&self.pool)
        .await?;

        row.map(|row| {
            Ok(SecretRecord {
                secret: Secret {
                    id: SecretId::new(row.id),
                    workspace_id: WorkspaceId::new(row.workspace_id),
                    name: row.name,
                    kind: deserialize_enum_text(&row.kind, "kind")?,
                    status: deserialize_enum_text(&row.status, "status")?,
                    current_version: from_db_version(row.current_version, "current_version")?,
                    created_at: row.created_at,
                    updated_at: row.updated_at,
                    last_used_at: row.last_used_at,
                },
            })
        })
        .transpose()
    }

    pub async fn get_current_secret_version(
        &self,
        workspace_id: &WorkspaceId,
        secret_id: &SecretId,
    ) -> Result<Option<SecretVersionRecord>, RegistryError> {
        let row = sqlx::query!(
            "select
                sv.secret_id,
                sv.version,
                sv.ciphertext,
                sv.key_version,
                sv.created_at as \"created_at!: time::OffsetDateTime\",
                sv.created_by
             from secrets s
             join secret_versions sv
               on sv.secret_id = s.id and sv.version = s.current_version
             where s.workspace_id = $1 and s.id = $2",
            workspace_id.as_str(),
            secret_id.as_str(),
        )
        .fetch_optional(&self.pool)
        .await?;

        row.map(|row| {
            Ok(SecretVersionRecord {
                secret_version: SecretVersion {
                    secret_id: SecretId::new(row.secret_id),
                    version: from_db_version(row.version, "version")?,
                    ciphertext: row.ciphertext,
                    key_version: row.key_version,
                    created_at: row.created_at,
                    created_by: row.created_by.map(UserId::new),
                },
            })
        })
        .transpose()
    }

    pub async fn create_secret(
        &self,
        request: CreateSecretRequest<'_>,
    ) -> Result<(), RegistryError> {
        let mut tx = self.pool.begin().await?;
        let result = sqlx::query(
            "insert into secrets (
                id,
                workspace_id,
                name,
                kind,
                status,
                current_version,
                last_used_at,
                created_at,
                updated_at
             ) values (
                $1, $2, $3, $4, $5, $6, $7::timestamptz, $8::timestamptz, $9::timestamptz
             )",
        )
        .bind(request.secret.id.as_str())
        .bind(request.secret.workspace_id.as_str())
        .bind(&request.secret.name)
        .bind(serialize_enum_text(&request.secret.kind, "kind")?)
        .bind(serialize_enum_text(&request.secret.status, "status")?)
        .bind(to_db_version(request.secret.current_version))
        .bind(request.secret.last_used_at)
        .bind(request.secret.created_at)
        .bind(request.secret.updated_at)
        .execute(&mut *tx)
        .await;

        match result {
            Ok(_) => {
                sqlx::query(
                    "insert into secret_versions (
                        secret_id,
                        version,
                        ciphertext,
                        key_version,
                        created_at,
                        created_by
                     ) values (
                        $1, $2, $3, $4, $5::timestamptz, $6
                     )",
                )
                .bind(request.secret.id.as_str())
                .bind(to_db_version(request.secret.current_version))
                .bind(request.ciphertext)
                .bind(request.key_version)
                .bind(request.secret.created_at)
                .bind(request.created_by.map(|value| value.as_str()))
                .execute(&mut *tx)
                .await?;
                tx.commit().await?;
                Ok(())
            }
            Err(sqlx::Error::Database(error))
                if error.constraint() == Some("secrets_workspace_name_idx") =>
            {
                Err(RegistryError::SecretNameAlreadyExists {
                    workspace_id: request.secret.workspace_id.as_str().to_owned(),
                    name: request.secret.name.clone(),
                })
            }
            Err(error) => Err(RegistryError::Storage(error)),
        }
    }

    pub async fn rotate_secret(
        &self,
        request: RotateSecretRequest<'_>,
    ) -> Result<SecretVersionRecord, RegistryError> {
        let existing = self
            .get_secret(request.workspace_id, request.secret_id)
            .await?
            .ok_or_else(|| RegistryError::SecretNotFound {
                secret_id: request.secret_id.as_str().to_owned(),
            })?;
        let next_version = existing.secret.current_version + 1;

        let mut tx = self.pool.begin().await?;
        sqlx::query(
            "insert into secret_versions (
                secret_id,
                version,
                ciphertext,
                key_version,
                created_at,
                created_by
             ) values (
                $1, $2, $3, $4, $5::timestamptz, $6
             )",
        )
        .bind(request.secret_id.as_str())
        .bind(to_db_version(next_version))
        .bind(request.ciphertext)
        .bind(request.key_version)
        .bind(request.created_at)
        .bind(request.created_by.map(|value| value.as_str()))
        .execute(&mut *tx)
        .await?;

        sqlx::query(
            "update secrets
                set current_version = $3,
                    updated_at = $4::timestamptz
              where workspace_id = $1 and id = $2",
        )
        .bind(request.workspace_id.as_str())
        .bind(request.secret_id.as_str())
        .bind(to_db_version(next_version))
        .bind(request.updated_at)
        .execute(&mut *tx)
        .await?;

        tx.commit().await?;

        Ok(SecretVersionRecord {
            secret_version: SecretVersion {
                secret_id: request.secret_id.clone(),
                version: next_version,
                ciphertext: request.ciphertext.to_owned(),
                key_version: request.key_version.to_owned(),
                created_at: *request.created_at,
                created_by: request.created_by.cloned(),
            },
        })
    }

    pub async fn delete_secret(
        &self,
        workspace_id: &WorkspaceId,
        secret_id: &SecretId,
    ) -> Result<(), RegistryError> {
        let result = sqlx::query(
            "delete from secrets
              where workspace_id = $1 and id = $2",
        )
        .bind(workspace_id.as_str())
        .bind(secret_id.as_str())
        .execute(&self.pool)
        .await?;

        if result.rows_affected() == 0 {
            return Err(RegistryError::SecretNotFound {
                secret_id: secret_id.as_str().to_owned(),
            });
        }

        Ok(())
    }

    pub async fn touch_secret(
        &self,
        workspace_id: &WorkspaceId,
        secret_id: &SecretId,
        used_at: &OffsetDateTime,
    ) -> Result<(), RegistryError> {
        let result = sqlx::query(
            "update secrets
                set last_used_at = $3::timestamptz
              where workspace_id = $1 and id = $2",
        )
        .bind(workspace_id.as_str())
        .bind(secret_id.as_str())
        .bind(used_at)
        .execute(&self.pool)
        .await?;

        if result.rows_affected() == 0 {
            return Err(RegistryError::SecretNotFound {
                secret_id: secret_id.as_str().to_owned(),
            });
        }

        Ok(())
    }
}
