use crate::AppService;
use crate::error::AppError;
use chrono::Utc;
use models::agents::model_provider;
use models::agents::{ModelStatus, model_provider::Entity as ProviderEntity};
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use utoipa::ToSchema;
use uuid::Uuid;

#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct CreateProviderRequest {
    pub name: String,
    pub display_name: String,
    pub website: Option<String>,
}

#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct UpdateProviderRequest {
    pub display_name: Option<String>,
    pub website: Option<String>,
    pub status: Option<String>,
}

#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct ProviderResponse {
    pub id: Uuid,
    pub name: String,
    pub display_name: String,
    pub website: Option<String>,
    pub status: String,
    pub created_at: chrono::DateTime<Utc>,
    pub updated_at: chrono::DateTime<Utc>,
}

impl From<model_provider::Model> for ProviderResponse {
    fn from(p: model_provider::Model) -> Self {
        Self {
            id: p.id,
            name: p.name,
            display_name: p.display_name,
            website: p.website,
            status: p.status,
            created_at: p.created_at,
            updated_at: p.updated_at,
        }
    }
}

pub(crate) fn require_system_caller(ctx: &Session) -> Result<(), AppError> {
    if ctx.user() != Some(Uuid::nil()) {
        return Err(AppError::Unauthorized);
    }
    Ok(())
}

impl AppService {
    pub async fn agent_provider_list(
        &self,
        _ctx: &Session,
    ) -> Result<Vec<ProviderResponse>, AppError> {
        let providers = ProviderEntity::find()
            .order_by_asc(model_provider::Column::DisplayName)
            .all(&self.db)
            .await?;
        Ok(providers.into_iter().map(ProviderResponse::from).collect())
    }

    pub async fn agent_provider_get(
        &self,
        id: Uuid,
        _ctx: &Session,
    ) -> Result<ProviderResponse, AppError> {
        let provider = ProviderEntity::find_by_id(id)
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Provider not found".to_string()))?;
        Ok(ProviderResponse::from(provider))
    }

    pub async fn agent_provider_create(
        &self,
        request: CreateProviderRequest,
        ctx: &Session,
    ) -> Result<ProviderResponse, AppError> {
        require_system_caller(ctx)?;

        let now = Utc::now();
        let active = model_provider::ActiveModel {
            id: Set(Uuid::now_v7()),
            name: Set(request.name),
            display_name: Set(request.display_name),
            website: Set(request.website),
            status: Set(ModelStatus::Active.to_string()),
            created_at: Set(now),
            updated_at: Set(now),
            ..Default::default()
        };
        let model = active.insert(&self.db).await?;
        Ok(ProviderResponse::from(model))
    }

    pub async fn agent_provider_update(
        &self,
        id: Uuid,
        request: UpdateProviderRequest,
        ctx: &Session,
    ) -> Result<ProviderResponse, AppError> {
        require_system_caller(ctx)?;

        let provider = ProviderEntity::find_by_id(id)
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Provider not found".to_string()))?;

        let mut active: model_provider::ActiveModel = provider.into();
        if let Some(display_name) = request.display_name {
            active.display_name = Set(display_name);
        }
        if let Some(website) = request.website {
            active.website = Set(Some(website));
        }
        if let Some(status) = request.status {
            active.status = Set(status);
        }
        active.updated_at = Set(Utc::now());

        let model = active.update(&self.db).await?;
        Ok(ProviderResponse::from(model))
    }

    pub async fn agent_provider_delete(&self, id: Uuid, ctx: &Session) -> Result<(), AppError> {
        require_system_caller(ctx)?;
        ProviderEntity::delete_by_id(id).exec(&self.db).await?;
        Ok(())
    }
}
