use std::fs::{self, File, OpenOptions};
use std::io::{Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};

use core_types::{BufferId, ErrorCode, ErrorDomain, RtError};
use memmap2::{Mmap, MmapMut};

use super::config::ShmTransportConfig;
use super::header::HEADER_SIZE;

pub(super) fn init_segment_files(cfg: &ShmTransportConfig) -> Result<(), RtError> {
    let file_size = HEADER_SIZE + cfg.segment_payload_bytes;
    for slot in 0..cfg.segment_count {
        let path = segment_path(cfg, slot);
        if !path.exists() {
            create_segment_file(&path, file_size)?;
            continue;
        }

        let md =
            fs::metadata(&path).map_err(|err| io_to_err("read segment metadata failed", err))?;
        if md.len() != file_size as u64 {
            create_segment_file(&path, file_size)?;
        }
    }
    Ok(())
}

pub(super) fn segment_path(cfg: &ShmTransportConfig, slot: usize) -> PathBuf {
    cfg.directory
        .join(format!("{}-slot-{}.shm", cfg.namespace, slot))
}

pub(super) fn slot_for(id: BufferId, segment_count: usize) -> usize {
    (id.0 as usize) % segment_count
}

pub(super) fn open_rw(path: &Path) -> Result<File, RtError> {
    OpenOptions::new()
        .read(true)
        .write(true)
        .open(path)
        .map_err(|err| io_to_err("open segment file failed", err))
}

pub(super) fn map_mut(file: &File) -> Result<MmapMut, RtError> {
    unsafe { MmapMut::map_mut(file) }.map_err(|err| io_to_err("map_mut failed", err))
}

pub(super) fn map_ro(file: &File) -> Result<Mmap, RtError> {
    unsafe { Mmap::map(file) }.map_err(|err| io_to_err("map read-only failed", err))
}

fn create_segment_file(path: &Path, file_size: usize) -> Result<(), RtError> {
    let mut file = OpenOptions::new()
        .create(true)
        .truncate(true)
        .read(true)
        .write(true)
        .open(path)
        .map_err(|err| io_to_err("create segment file failed", err))?;
    file.seek(SeekFrom::Start((file_size - 1) as u64))
        .map_err(|err| io_to_err("seek segment file failed", err))?;
    file.write_all(&[0u8])
        .map_err(|err| io_to_err("extend segment file failed", err))?;
    file.flush()
        .map_err(|err| io_to_err("flush segment file failed", err))?;
    Ok(())
}

pub(super) fn io_to_err(prefix: &str, err: std::io::Error) -> RtError {
    RtError::new(
        ErrorCode::Disconnected,
        ErrorDomain::Core,
        true,
        format!("{prefix}: {err}"),
    )
}
