use std::fs::{self, OpenOptions};

use core_types::{ErrorCode, ErrorDomain, RtError};

use super::config::validate_cfg;
use super::header::{HEADER_SIZE, read_header};
use super::segment_io::{init_segment_files, io_to_err, map_ro, segment_path, slot_for};
use super::{SharedMemoryBufferRef, SharedMemoryLease, SharedMemoryReader, ShmTransportConfig};

impl SharedMemoryReader {
    pub fn new(cfg: ShmTransportConfig) -> Result<Self, RtError> {
        validate_cfg(&cfg)?;
        fs::create_dir_all(&cfg.directory)
            .map_err(|err| io_to_err("create shm directory failed", err))?;
        init_segment_files(&cfg)?;
        Ok(Self { cfg })
    }

    pub fn loan(&self, reference: SharedMemoryBufferRef) -> Result<SharedMemoryLease, RtError> {
        if reference.len > self.cfg.segment_payload_bytes {
            return Err(RtError::new(
                ErrorCode::InvalidState,
                ErrorDomain::Core,
                false,
                "invalid shared memory reference length",
            ));
        }

        let slot = slot_for(reference.buffer_id, self.cfg.segment_count);
        let segment_path = segment_path(&self.cfg, slot);
        let file = OpenOptions::new()
            .read(true)
            .open(&segment_path)
            .map_err(|err| io_to_err("open shm segment for read failed", err))?;
        let mmap = map_ro(&file)?;

        let (buf_id, len) = read_header(&mmap)?;
        if buf_id != reference.buffer_id {
            return Err(RtError::new(
                ErrorCode::InvalidState,
                ErrorDomain::Core,
                false,
                format!(
                    "stale shm slot content: expected buffer_id={} actual={}",
                    reference.buffer_id, buf_id
                ),
            ));
        }

        if len != reference.len {
            return Err(RtError::new(
                ErrorCode::InvalidState,
                ErrorDomain::Core,
                false,
                format!(
                    "shm payload length mismatch: expected={} actual={}",
                    reference.len, len
                ),
            ));
        }

        let start = HEADER_SIZE + reference.offset;
        let end = start + reference.len;
        if end > HEADER_SIZE + self.cfg.segment_payload_bytes {
            return Err(RtError::new(
                ErrorCode::InvalidState,
                ErrorDomain::Core,
                false,
                "shm reference range out of bounds",
            ));
        }

        Ok(SharedMemoryLease {
            mmap,
            offset: start,
            len: reference.len,
        })
    }
}
