use std::io::ErrorKind;
use std::io::{Error, Read, Seek, SeekFrom, Write};
use std::time::{SystemTime, UNIX_EPOCH};

use chrono::{DateTime, Local, TimeZone};

use crate::util::errors::AppError;
use crate::VERSION;

pub const PREFIX: &[u8] = b"squish";

pub fn magic_version() -> Vec<u8> {
    [PREFIX, VERSION.as_bytes()].concat()
}

/// Write the header to a archive file
///
/// # arguments
///
/// * 'writer' - writer instance of the archive file
///
/// # returns
///
/// * 'std::io::Result<()>' - Error indicating issue writing to the file
///
/// # examples
///
/// ```
/// use squishrs::util::header::write_header;
/// use std::io::Cursor;
///
/// let mut writer = Cursor::new(Vec::new());
/// write_header(&mut writer).expect("Failed to write header");
/// ```
pub fn write_header<W: Write>(writer: &mut W) -> std::io::Result<()> {
    let magic_version = magic_version();
    writer.write_all(&magic_version)
}

/// Writes the current system time as a little-endian
/// 64-bit unsigned integer representing seconds since the UNIX epoch
/// into the provided writer.
///
/// # Arguments
///
/// * `writer` - A mutable reference to a writer implementing the `Write` trait.
///
/// # Errors
///
/// Returns an `std::io::Error` if writing to the writer fails.
///
/// # Panics
///
/// Panics if the system time is before the UNIX epoch (should not happen on normal systems).
pub fn write_timestamp<W: Write>(writer: &mut W) -> std::io::Result<()> {
    // Get current system time as seconds since UNIX epoch
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("System time before UNIX");
    let timestamp = now.as_secs();

    writer.write_all(&timestamp.to_le_bytes())
}

/// Converts a UNIX timestamp (seconds since epoch) into a formatted
/// local date and time string.
///
/// The returned string is formatted as result `"HH:MM DD/MM/YYYY"`.
///
/// # Arguments
///
/// * `timestamp_sec` - The timestamp in seconds since the UNIX epoch.
///
/// # Panics
///
/// Panics if the timestamp is invalid or cannot be converted to a single
/// valid local datetime.
///
/// # Examples
///
/// ```
/// use squishrs::util::header::convert_timestamp_to_date;
///
/// let formatted_date = convert_timestamp_to_date(1686890000).expect("Cannot convert to date");
/// println!("{}", formatted_date); // e.g. "17:49 16/06/2025"
/// ```
pub fn convert_timestamp_to_date(timestamp_sec: u64) -> Result<String, AppError> {
    let datetime: DateTime<Local> = Local
        .timestamp_opt(timestamp_sec as i64, 0)
        .single()
        .ok_or_else(|| {
            AppError::InvalidTimeStamp(Error::new(ErrorKind::InvalidInput, "Invalid timestamp"))
        })?;
    Ok(datetime.format("%H:%M %d/%m/%Y").to_string())
}

/// Verify the header of an archive
///
/// # arguments
///
/// * 'reader' - reader instance of the archive file
///
/// # returns
///
/// * 'std::io::Result<()>' - Error indicating the archive header is invalid
///
/// # examples
///
/// ```
/// use squishrs::util::header::{write_header, verify_header};
/// use std::io::Cursor;
///
/// let mut buffer = Cursor::new(Vec::new());
/// write_header(&mut buffer).expect("Failed to write header");
/// buffer.set_position(0);
///
/// verify_header(&mut buffer).expect("Invalid Header");
/// ```
pub fn verify_header<R: Read>(reader: &mut R) -> Result<String, AppError> {
    // Allocate buffer for prefix + version (prefix + 8 bytes for "00.01.01" format)
    let expected_len = magic_version().len();
    let mut header = vec![0u8; expected_len];
    reader.read_exact(&mut header)?;

    // Check prefix
    if !header.starts_with(PREFIX) {
        return Err(AppError::Archive(
            "Invalid archive header: prefix mismatch".into(),
        ));
    }

    // Extract version bytes after prefix
    let version_bytes = &header[PREFIX.len()..];
    let version_str = std::str::from_utf8(version_bytes)
        .map_err(|_| AppError::Archive("Invalid UTF-8 in version string".into()))?;

    // Parse major and minor from header version
    let header_parts: Vec<&str> = version_str.split('.').collect();
    if header_parts.len() < 2 {
        return Err(AppError::Archive(
            "Invalid version format in archive header".into(),
        ));
    }
    let header_major = header_parts[0];
    let header_minor = header_parts[1];

    // Parse major and minor from current VERSION
    let current_parts: Vec<&str> = VERSION.split('.').collect();
    if current_parts.len() < 2 {
        return Err(AppError::Other("Current version is malformed".into()));
    }
    let current_major = current_parts[0];
    let current_minor = current_parts[1];

    // Compare major and minor versions
    if header_major != current_major || header_minor != current_minor {
        return Err(AppError::Archive(format!(
            "Incompatible version: archive {header_major}.{header_minor} vs current {current_major}.{current_minor}"
        )));
    }

    Ok(version_str.to_string())
}

/// Writes a placeholder `u64` (8 zero bytes) to the writer and returns its stream position.
///
/// This function is useful when the actual value (e.g., number of items written) is not yet known.
/// You can later overwrite this placeholder using [`patch_u64`].
///
/// # Arguments
///
/// * `writer` - A mutable reference to any writer that implements `Write + Seek`.
///
/// # Returns
///
/// * `Ok(u64)` - The byte offset in the stream where the placeholder was written.
/// * `Err` - If writing or getting the stream position fails.
///
/// # Example
///
/// ```rust
/// use std::io::Cursor;
/// use squishrs::util::header::patch_u64;
/// use squishrs::util::header::write_placeholder_u64;
///
/// let mut writer = Cursor::new(Vec::new());
/// let pos = write_placeholder_u64(&mut writer).expect("Failed to write placeholder");
/// // ... later ...
/// let actual_value = 2;
/// patch_u64(&mut writer, pos, actual_value).expect("Failed to patch int");
/// ```
pub fn write_placeholder_u64<W: Write + Seek>(writer: &mut W) -> Result<u64, std::io::Error> {
    let pos = writer.stream_position()?;
    writer.write_all(&0u64.to_le_bytes())?;
    Ok(pos)
}

/// Overwrites a `u64` value at a previously recorded position in the writer stream.
///
/// This is typically used to update a placeholder written earlier with [`write_placeholder_u64`].
/// After writing the value, the stream is moved to the end to resume normal writing.
///
/// # Arguments
///
/// * `writer` - A mutable reference to a writer that implements `Write + Seek`.
/// * `pos` - The byte offset at which to write the new `u64` value.
/// * `value` - The actual `u64` value to write.
///
/// # Returns
///
/// * `Ok(())` - If the patch was successful.
/// * `Err` - If seeking or writing fails.
///
/// # Example
///
/// ```rust
/// use squishrs::util::header::patch_u64;
/// use std::io::{Seek, SeekFrom, Cursor};
///
/// let mut writer = Cursor::new(Vec::new());
/// let pos = writer.seek(SeekFrom::Current(0)).unwrap();
/// patch_u64(&mut writer, pos, 1234).expect("Failed to patch value");
/// ```
pub fn patch_u64<W: Write + Seek>(
    writer: &mut W,
    pos: u64,
    value: u64,
) -> Result<(), std::io::Error> {
    writer.seek(SeekFrom::Start(pos))?;
    writer.write_all(&value.to_le_bytes())?;
    writer.seek(SeekFrom::End(0))?;
    Ok(())
}
