use sqlx::PgPool;
use tokio::sync::broadcast;

use crate::config::AppConfig;
use crate::services::compression::CompressionWorker;
use crate::services::config::ConfigCache;
use crate::services::disk::DiskCache;
use crate::services::media_total::MediaTotalCache;
use crate::services::rate_limiter::RateLimiter;
use crate::services::sse_tickets::SseTicketStore;
use crate::services::upload_admission::UploadAdmission;

#[derive(Clone, Debug)]
pub struct SseEvent {
    pub event_type: String,
    pub data: String,
}

impl SseEvent {
    /// Standardised constructor. Prefer this over building the struct inline so the
    /// event-type strings stay consistent across handlers.
    pub fn new(event_type: impl Into<String>, data: impl Into<String>) -> Self {
        Self {
            event_type: event_type.into(),
            data: data.into(),
        }
    }
}

#[derive(Clone)]
pub struct AppState {
    pub pool: PgPool,
    pub config: AppConfig,
    pub sse_tx: broadcast::Sender<SseEvent>,
    pub compression: CompressionWorker,
    pub rate_limiter: RateLimiter,
    pub sse_tickets: SseTicketStore,
    /// In-memory cache in front of the `config` table. Reads go through here; the
    /// admin PATCH handler and the test reseed invalidate it after committing.
    pub config_cache: ConfigCache,
    /// Cached total/free bytes for the media filesystem (quota + admin stats).
    pub disk_cache: DiskCache,
    /// Cached sum of all media bytes, for the upload gate's keepsake-headroom check.
    pub media_total: MediaTotalCache,
    /// Byte budget for upload bodies currently streaming to temp files. The headroom gate can
    /// only refuse to COMMIT bytes that are already on disk; this is what bounds how many get
    /// there at once.
    pub upload_admission: UploadAdmission,
}

impl AppState {
    pub fn new(pool: PgPool, config: AppConfig) -> Self {
        // Broadcast buffer for live SSE fan-out. Sized to absorb a burst (e.g. many
        // uploads landing at once during a busy moment) before a slow consumer lags and
        // has to `resync`. The resync path is a correctness backstop, not the happy path —
        // a roomier buffer keeps ~1000 concurrent clients from all resyncing at once.
        let (sse_tx, _) = broadcast::channel(1024);
        let compression = CompressionWorker::new(
            pool.clone(),
            config.media_path.clone(),
            config.compression_concurrency,
            sse_tx.clone(),
        );
        let config_cache = ConfigCache::new(pool.clone());
        Self {
            pool,
            config,
            sse_tx,
            compression,
            rate_limiter: RateLimiter::new(),
            sse_tickets: SseTicketStore::new(),
            config_cache,
            disk_cache: DiskCache::new(),
            media_total: MediaTotalCache::new(),
            upload_admission: UploadAdmission::new(),
        }
    }
}
