#![deny(warnings)]
#![allow(dead_code)]
//! [TSM.ID].[11031972] -- Platform X Ecosystem
//! xcu-ingest -- Media Ingestion Server (RTMP/HLS/DASH)
pub mod server;
use std::collections::HashMap;

#[derive(Debug)]
pub enum IngestError { StreamNotFound(String), TranscodeFailed(String), BufferFull(String) }
impl std::fmt::Display for IngestError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self { Self::StreamNotFound(e) => write!(f, "Stream: {e}"), Self::TranscodeFailed(e) => write!(f, "Transcode: {e}"), Self::BufferFull(e) => write!(f, "Buffer: {e}") }
    }
}
impl std::error::Error for IngestError {}

#[derive(Debug, Clone)]
pub struct StreamConfig { pub stream_id: String, pub codec: String, pub bitrate_kbps: u32, pub width: u32, pub height: u32, pub fps: u32 }

#[derive(Debug, Clone)]
pub struct MediaChunk { pub sequence: u64, pub data: Vec<u8>, pub duration_ms: u32, pub keyframe: bool, pub timestamp: u64 }

pub struct IngestPipeline {
    streams: HashMap<String, StreamState>,
    max_buffer_chunks: usize,
}

struct StreamState { config: StreamConfig, buffer: Vec<MediaChunk>, total_bytes: u64, chunk_count: u64 }

impl IngestPipeline {
    pub fn new(max_buffer: usize) -> Self { Self { streams: HashMap::new(), max_buffer_chunks: max_buffer } }

    pub fn create_stream(&mut self, config: StreamConfig) -> Result<(), IngestError> {
        let id = config.stream_id.clone();
        self.streams.insert(id, StreamState { config, buffer: Vec::new(), total_bytes: 0, chunk_count: 0 });
        Ok(())
    }

    pub fn push_chunk(&mut self, stream_id: &str, chunk: MediaChunk) -> Result<u64, IngestError> {
        let state = self.streams.get_mut(stream_id).ok_or_else(|| IngestError::StreamNotFound(stream_id.into()))?;
        if state.buffer.len() >= self.max_buffer_chunks {
            state.buffer.remove(0); // Drop oldest (sliding window)
        }
        state.total_bytes += chunk.data.len() as u64;
        state.chunk_count += 1;
        let seq = state.chunk_count;
        state.buffer.push(chunk);
        Ok(seq)
    }

    /// Generate HLS playlist from buffer
    pub fn generate_hls_playlist(&self, stream_id: &str) -> Result<String, IngestError> {
        let state = self.streams.get(stream_id).ok_or_else(|| IngestError::StreamNotFound(stream_id.into()))?;
        let mut m3u8 = String::from("#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:4\n");
        for chunk in &state.buffer {
            m3u8.push_str(&format!("#EXTINF:{:.3},\n", chunk.duration_ms as f64 / 1000.0));
            m3u8.push_str(&format!("segment_{}.ts\n", chunk.sequence));
        }
        Ok(m3u8)
    }

    /// Get stream stats
    pub fn stream_stats(&self, stream_id: &str) -> Result<(u64, u64, f64), IngestError> {
        let state = self.streams.get(stream_id).ok_or_else(|| IngestError::StreamNotFound(stream_id.into()))?;
        let bitrate = if state.chunk_count > 0 { (state.total_bytes * 8) as f64 / (state.chunk_count as f64 * 4.0) / 1000.0 } else { 0.0 };
        Ok((state.chunk_count, state.total_bytes, bitrate))
    }

    pub fn active_streams(&self) -> usize { self.streams.len() }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_ingest() {
        let mut p = IngestPipeline::new(10);
        p.create_stream(StreamConfig { stream_id: "s1".into(), codec: "h264".into(), bitrate_kbps: 3000, width: 1920, height: 1080, fps: 30 }).unwrap();
        for i in 0..5 {
            p.push_chunk("s1", MediaChunk { sequence: i, data: vec![0; 1000], duration_ms: 4000, keyframe: i == 0, timestamp: i * 4000 }).unwrap();
        }
        let playlist = p.generate_hls_playlist("s1").unwrap();
        assert!(playlist.contains("#EXTM3U"));
        assert!(playlist.contains("segment_"));
    }
}
