use crate::serial_core::codecs::test::{export_recording_csv, TestCodec, TestCsvImporter, TestHandler};
use crate::serial_core::error::SerialError;
use crate::serial_core::record::CsvImporter;
use crate::serial_core::{TestRecording, serial};
use log::info;
use serde::Serialize;
use std::fs::File;
use std::io::Cursor;
use std::sync::{Arc, Mutex};
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use tauri::{async_runtime::JoinHandle, AppHandle, Manager, State};
use tokio_serial::{available_ports, SerialPortBuilderExt};
use tokio_util::sync::CancellationToken;

type SharedTestRecording = Arc<Mutex<TestRecording>>;


#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SerialConnectResponse {
    pub port: String,
    pub connected: bool,
    pub message: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SerialExportResponse {
    pub path: String,
    pub frame_count: usize,
    pub message: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SerialImportFrame {
    pub data: Vec<i32>,
    pub dts_ms: u64,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SerialImportResponse {
    pub file_name: String,
    pub frame_count: usize,
    pub channel_count: usize,
    pub frames: Vec<SerialImportFrame>,
    pub message: String,
}

struct SerialSession {
    port: String,
    cancel: CancellationToken,
    task: JoinHandle<()>,
    current_record: SharedTestRecording,
}

#[derive(Default)]
pub struct SerialConnectionState {
    session: Mutex<Option<SerialSession>>,
    last_record: Mutex<Option<SharedTestRecording>>
}

#[tauri::command]
pub fn serial_enum() -> Result<Vec<String>, SerialError> {
    let ports = available_ports()
        .map_err(|_| SerialError::ScanError)?
        .into_iter()
        .map(|info| info.port_name)
        .collect();

    Ok(ports)
}

#[tauri::command]
pub async fn serial_connect(
    app: AppHandle,
    port: String,
    state: State<'_, SerialConnectionState>,
) -> Result<SerialConnectResponse, SerialError> {
    let port_name = port.trim().to_string();
    if port_name.is_empty() {
        return Err(SerialError::InvalidConfig);
    }

    {
        let session = state.session.lock().map_err(|_| SerialError::StateError)?;
        if session.is_some() {
            return Err(SerialError::AlreadyConnected);
        }
    }

    let cancel = CancellationToken::new();
    let current_record = Arc::new(Mutex::new(TestRecording::new()));
    let task_record = current_record.clone();
    let task_cancel = cancel.clone();
    let task_app = app.clone();
    let task_port_name = port_name.clone();

    let port = tokio_serial::new(&port_name, 115200)
        .open_native_async()
        .map_err(|_| SerialError::OpenError)?;
    let session_started_at = Instant::now();

    let task = tauri::async_runtime::spawn(async move {
        let codec = TestCodec::new();
        let handler = TestHandler;

        if let Err(error) = serial::run_serial(
            task_app.clone(),
            port,
            codec,
            handler,
            session_started_at,
            task_record.clone(),
            task_cancel,
        )
        .await
        {
            eprintln!("serial task exited with error: {error}");
        }

        let manager = task_app.state::<SerialConnectionState>();
        if let Ok(mut last_record) = manager.last_record.lock() {
            *last_record = Some(task_record);
        }

        let mut session = match manager.session.lock() {
            Ok(session) => session,
            Err(_) => return,
        };

        {
            let should_clear = session
                .as_ref()
                .map(|current| current.port.as_str() == task_port_name.as_str())
                .unwrap_or(false);

            if should_clear {
                session.take();
            }
        }
    });

    let mut session = state.session.lock().map_err(|_| SerialError::StateError)?;
    if session.is_some() {
        cancel.cancel();
        task.abort();
        return Err(SerialError::AlreadyConnected);
    }

    *session = Some(SerialSession {
        port: port_name.clone(),
        cancel,
        task,
        current_record
    });

    Ok(SerialConnectResponse {
        port: port_name,
        connected: true,
        message: "connected".to_string(),
    })
}

#[tauri::command]
pub async fn serial_disconnect(
    state: State<'_, SerialConnectionState>,
) -> Result<SerialConnectResponse, SerialError> {
    let session = {
        let mut guard = state.session.lock().map_err(|_| SerialError::StateError)?;
        guard.take()
    };

    let Some(SerialSession {
        port,
        cancel,
        task,
        current_record,
    }) = session
    else {
        return Ok(SerialConnectResponse {
            port: String::new(),
            connected: false,
            message: "already disconnected".to_string(),
        });
    };

    cancel.cancel();
    let _ = task.await;
    let frame_count = current_record.lock().map(|record| {
        record.frames.len()
    }).unwrap_or(0);

    info!("last_record has {} frames", frame_count);

    if let Ok(mut last_record) = state.last_record.lock() {
        *last_record = Some(current_record);
    }


    Ok(SerialConnectResponse {
        port,
        connected: false,
        message: "disconnected".to_string(),
    })
}

#[tauri::command]
pub fn serial_export_csv(
    app: AppHandle,
    state: State<'_, SerialConnectionState>,
) -> Result<SerialExportResponse, SerialError> {
    let current_record = {
        let session = state.session.lock().map_err(|_| SerialError::StateError)?;
        session
            .as_ref()
            .map(|current_session| current_session.current_record.clone())
    };

    let record = if let Some(recording) = current_record {
        recording
    } else {
        let last_record = state.last_record.lock().map_err(|_| SerialError::StateError)?;
        last_record.clone().ok_or(SerialError::NoRecordedData)?
    };

    let mut output_dir = match app.path().desktop_dir() {
        Ok(path) => path,
        Err(_) => std::env::current_dir().map_err(|_| SerialError::ExportError)?,
    };

    let timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|duration| duration.as_millis())
        .unwrap_or_default();

    output_dir.push(format!("joyson_export_{timestamp}.csv"));
    let mut file = File::create(&output_dir).map_err(|_| SerialError::ExportError)?;

    let frame_count = {
        let recording = record.lock().map_err(|_| SerialError::StateError)?;
        if recording.frames.is_empty() {
            return Err(SerialError::NoRecordedData);
        }

        export_recording_csv(&recording, &mut file).map_err(|_| SerialError::ExportError)?;
        recording.frames.len()
    };

    let path = output_dir.display().to_string();
    info!("csv exported to {path}, frame_count={frame_count}");

    Ok(SerialExportResponse {
        path,
        frame_count,
        message: "exported".to_string(),
    })
}

#[tauri::command]
pub fn serial_import_csv(file_name: String, csv_content: String) -> Result<SerialImportResponse, SerialError> {
    let mut importer = TestCsvImporter::new(file_name.as_str());
    let packets = importer
        .load(Cursor::new(csv_content.into_bytes()))
        .map_err(|_| SerialError::ImportError)?;

    if packets.is_empty() {
        return Err(SerialError::NoRecordedData);
    }

    let channel_count = packets.first().map(|item| item.data.len()).unwrap_or(0);
    let frame_count = packets.len();
    let frames = packets
        .into_iter()
        .map(|packet| SerialImportFrame {
            data: packet.data,
            dts_ms: packet.dts_ms,
        })
        .collect();

    Ok(SerialImportResponse {
        file_name,
        frame_count,
        channel_count,
        frames,
        message: "imported".to_string(),
    })
}
