use anyhow::{Context, Result, bail};
use gstreamer as gst;
use gstreamer::prelude::*;
use gstreamer_app as gst_app;
use lesavka_common::lesavka::{AudioPacket, OutputDelayProbeRequest, VideoPacket};
use serde::Serialize;
use std::f64::consts::TAU;
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

use crate::audio::Voice;
use crate::camera::{CameraCodec, CameraConfig};
use crate::video::CameraRelay;

const DEFAULT_DURATION_SECONDS: u32 = 20;
const DEFAULT_WARMUP_SECONDS: u32 = 4;
const DEFAULT_PULSE_PERIOD_MS: u32 = 1_000;
const DEFAULT_PULSE_WIDTH_MS: u32 = 120;
const DEFAULT_EVENT_WIDTH_CODES: &[u32] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
const AUDIO_SAMPLE_RATE: u32 = 48_000;
const AUDIO_CHANNELS: usize = 2;
const AUDIO_CHUNK_MS: u64 = 10;
const AUDIO_AMPLITUDE: f64 = 24_000.0;
const AUDIO_PILOT_AMPLITUDE: f64 = 700.0;
const AUDIO_PILOT_FREQUENCY_HZ: f64 = 180.0;
const DARK_FRAME_RGB: Rgb = Rgb { r: 4, g: 8, b: 12 };
const VIDEO_CONTINUITY_BLOCKS: usize = 20;
const VIDEO_CONTINUITY_DATA_BITS: usize = 16;
const EVENT_COLORS: [Rgb; 16] = [
    Rgb {
        r: 255,
        g: 45,
        b: 45,
    },
    Rgb {
        r: 0,
        g: 230,
        b: 118,
    },
    Rgb {
        r: 41,
        g: 121,
        b: 255,
    },
    Rgb {
        r: 255,
        g: 179,
        b: 0,
    },
    Rgb {
        r: 216,
        g: 27,
        b: 96,
    },
    Rgb {
        r: 0,
        g: 188,
        b: 212,
    },
    Rgb {
        r: 205,
        g: 220,
        b: 57,
    },
    Rgb {
        r: 126,
        g: 87,
        b: 194,
    },
    Rgb {
        r: 255,
        g: 112,
        b: 67,
    },
    Rgb {
        r: 38,
        g: 166,
        b: 154,
    },
    Rgb {
        r: 255,
        g: 64,
        b: 129,
    },
    Rgb {
        r: 92,
        g: 107,
        b: 192,
    },
    Rgb {
        r: 255,
        g: 235,
        b: 59,
    },
    Rgb {
        r: 105,
        g: 240,
        b: 174,
    },
    Rgb {
        r: 171,
        g: 71,
        b: 188,
    },
    Rgb {
        r: 3,
        g: 169,
        b: 244,
    },
];
const EVENT_FREQUENCIES_HZ: [f64; 16] = [
    620.0, 780.0, 940.0, 1_120.0, 1_320.0, 1_540.0, 1_780.0, 2_040.0, 2_320.0, 2_620.0, 2_960.0,
    3_340.0, 3_760.0, 4_220.0, 4_740.0, 5_320.0,
];

#[derive(Clone, Copy, Debug)]
struct Rgb {
    r: u8,
    g: u8,
    b: u8,
}

#[derive(Clone, Debug)]
struct ProbeConfig {
    duration: Duration,
    warmup: Duration,
    pulse_period: Duration,
    pulse_width: Duration,
    event_width_codes: Vec<u32>,
    audio_delay: Duration,
    video_delay: Duration,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OutputDelayProbeSummary {
    pub video_frames: u64,
    pub audio_packets: u64,
    pub event_count: u64,
    pub timeline_json: String,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct ProbeEventSlot {
    event_id: usize,
    code: u32,
    planned_start_us: u64,
    planned_end_us: u64,
}

#[derive(Clone, Debug, Serialize)]
struct OutputDelayProbeTimeline {
    schema: &'static str,
    origin: &'static str,
    media_path: &'static str,
    injection_scope: &'static str,
    server_pipeline_reference: &'static str,
    sink_handoff_path: &'static str,
    client_uplink_included: bool,
    camera_width: u32,
    camera_height: u32,
    camera_fps: u32,
    audio_sample_rate: u32,
    audio_channels: usize,
    audio_chunk_ms: u64,
    audio_delay_us: u64,
    video_delay_us: u64,
    server_start_unix_ns: u128,
    pulse_period_ms: u64,
    pulse_width_ms: u64,
    warmup_us: u64,
    duration_us: u64,
    events: Vec<OutputDelayProbeEventTimeline>,
}

#[derive(Clone, Debug, Serialize)]
struct OutputDelayProbeEventTimeline {
    event_id: usize,
    code: u32,
    planned_start_us: u64,
    planned_end_us: u64,
    video_seq: Option<u64>,
    audio_seq: Option<u64>,
    video_feed_monotonic_us: Option<u64>,
    audio_push_monotonic_us: Option<u64>,
    video_feed_unix_ns: Option<u128>,
    audio_push_unix_ns: Option<u128>,
    server_feed_delta_ms: Option<f64>,
}

include!("output_delay_probe/timeline_config.rs");
include!("output_delay_probe/probe_runtime.rs");
include!("output_delay_probe/media_encoding.rs");

#[cfg(test)]
#[path = "output_delay_probe/tests/mod.rs"]
mod tests;
