/*
 * authservice db/announcements.rs
 * - pgsql announcement functions
 *
 * Copyright (C) 2025 Real Microsoft, LLC
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

use log::error;
use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, IntoActiveModel, QueryFilter, QueryOrder};
use sea_orm::ActiveValue::Set;
use entity::announcement;

#[derive(Debug)]
pub enum AnnouncementInsertError {
    SlugInUse,
    DatabaseError,
}

pub async fn create_announcement(db: &DatabaseConnection, slug: String, title: String, short: String, full: String, creator_uid: String) -> Result<(), AnnouncementInsertError> {
    if announcement::Entity::find().filter(announcement::Column::Slug.eq(&slug)).one(db).await.map_err(|e| {
        error!("DATABASE ERROR WHILE CREATEANNOUNCEMENT (check if slug free): {e}");
        AnnouncementInsertError::DatabaseError
    })?.is_some() {
        return Err(AnnouncementInsertError::SlugInUse);
    }

    let record = announcement::ActiveModel {
        slug: Set(slug),
        title: Set(title),
        short_text_notice: Set(short),
        full_markdown_notice: Set(full),
        creator_id: Set(creator_uid),
        created_on: Set(chrono::Utc::now().naive_utc()),
        updated_on: Default::default(),
        flags: Set(0),
    };

    record.insert(db).await.map_err(|e| {
        error!("DATABASE ERROR WHILE CREATEANNOUNCEMENT (insert): {e}");
        AnnouncementInsertError::DatabaseError
    })?;

    Ok(())
}

#[derive(Debug)]
pub enum AnnouncementUpdateError {
    SlugNotFound,
    DatabaseError,
}

pub async fn update_announcement(db: &DatabaseConnection, slug: String, title: String, short: String, full: String) -> Result<(), AnnouncementUpdateError> {
    if let Some(record) = announcement::Entity::find_by_id(&slug).one(db).await.map_err(|e| {
        error!("DATABASE ERROR WHILE UPDATEANNOUNCEMENT (find): {e}");
        AnnouncementUpdateError::DatabaseError
    })? {
        let mut record = record.into_active_model();
        record.title = Set(title);
        record.short_text_notice = Set(short);
        record.full_markdown_notice = Set(full);
        record.updated_on = Set(Some(chrono::Utc::now().naive_utc()));

        record.update(db).await.map_err(|e| {
            error!("DATABASE ERROR WHILE UPDATEANNOUNCEMENT (update): {e}");
            AnnouncementUpdateError::DatabaseError
        })?;

        Ok(())
    } else {
        Err(AnnouncementUpdateError::SlugNotFound)
    }
}

pub struct AnnouncementRecord {
    pub slug: String,
    pub title: String,
    pub short: String,
    pub full: String,
    pub creator_uid: String,
    pub created_on: chrono::DateTime<chrono::Utc>,
    pub updated_on: Option<chrono::DateTime<chrono::Utc>>,
}

#[derive(Debug)]
pub enum AnnouncementFetchError {
    SlugNotFound,
    DatabaseError,
}

pub async fn get_announcement(db: &DatabaseConnection, slug: String) -> Result<AnnouncementRecord, AnnouncementFetchError> {
    Ok(announcement::Entity::find_by_id(slug).one(db).await.map_err(|e| {
        error!("DATABASE ERROR WHILE GETANNOUNCEMENT: {e}");
        AnnouncementFetchError::DatabaseError
    })?.ok_or(AnnouncementFetchError::SlugNotFound).map(|v| {
        AnnouncementRecord {
            slug: v.slug,
            title: v.title,
            short: v.short_text_notice,
            full: v.full_markdown_notice,
            creator_uid: v.creator_id,
            created_on: v.created_on.and_utc(),
            updated_on: v.updated_on.map(|v| v.and_utc()),
        }
    })?)
}

pub async fn get_all_announcements(db: &DatabaseConnection) -> Result<Vec<AnnouncementRecord>, AnnouncementFetchError> {
    Ok(announcement::Entity::find().order_by_desc(announcement::Column::CreatedOn).all(db).await.map_err(|e| {
        error!("DATABASE ERROR WHILE ALLANNOUNCEMENT: {e}");
        AnnouncementFetchError::DatabaseError
    })?.into_iter().map(|v| {
        AnnouncementRecord {
            slug: v.slug,
            title: v.title,
            short: v.short_text_notice,
            full: v.full_markdown_notice,
            creator_uid: v.creator_id,
            created_on: v.created_on.and_utc(),
            updated_on: v.updated_on.map(|v| v.and_utc()),
        }
    }).collect())
}

pub async fn latest_announcement(db: &DatabaseConnection) -> Result<AnnouncementRecord, AnnouncementFetchError> {
    Ok(announcement::Entity::find().order_by_desc(announcement::Column::CreatedOn).one(db).await.map_err(|e| {
        error!("DATABASE ERROR WHILE LATESTANNOUNCEMENT: {e}");
        AnnouncementFetchError::DatabaseError
    })?.ok_or(AnnouncementFetchError::SlugNotFound).map(|v| {
        AnnouncementRecord {
            slug: v.slug,
            title: v.title,
            short: v.short_text_notice,
            full: v.full_markdown_notice,
            creator_uid: v.creator_id,
            created_on: v.created_on.and_utc(),
            updated_on: v.updated_on.map(|v| v.and_utc()),
        }
    })?)
}