const IDEMPOTENCY_RECORD_SCHEMA_VERSION: &str = "v2";
const IDEMPOTENCY_RECORD_SCHEMA_VERSION_V1: &str = "v1";

pub(super) fn encode_hex(raw: &[u8]) -> String {
    let mut out = String::with_capacity(raw.len() * 2);
    for byte in raw {
        use std::fmt::Write as _;
        let _ = write!(&mut out, "{byte:02x}");
    }
    out
}

pub(super) fn decode_hex(raw: &str) -> Result<Vec<u8>, ()> {
    if !raw.len().is_multiple_of(2) {
        return Err(());
    }
    let mut out = Vec::with_capacity(raw.len() / 2);
    for idx in (0..raw.len()).step_by(2) {
        let byte = u8::from_str_radix(&raw[idx..idx + 2], 16).map_err(|_| ())?;
        out.push(byte);
    }
    Ok(out)
}

pub(super) fn render_idempotency_disk_line(
    kind: char,
    created_at_ms: i64,
    request_id: &str,
    payload: &[u8],
) -> String {
    format!(
        "{IDEMPOTENCY_RECORD_SCHEMA_VERSION}\t{kind}\t{created_at_ms}\t{}\t{}",
        encode_hex(request_id.as_bytes()),
        encode_hex(payload)
    )
}

pub(super) fn parse_idempotency_disk_record(line: &str) -> Option<(String, i64, String, String)> {
    if let Some(versioned) = line.strip_prefix("v2\t") {
        let mut fields = versioned.splitn(4, '\t');
        let kind = fields.next()?;
        let created_at_ms = fields.next()?.parse::<i64>().ok()?;
        let request_id_hex = fields.next()?;
        let payload_hex = fields.next()?;
        let request_id = String::from_utf8(decode_hex(request_id_hex).ok()?).ok()?;
        return Some((
            kind.to_string(),
            created_at_ms,
            request_id,
            payload_hex.to_string(),
        ));
    }
    if let Some(versioned) = line.strip_prefix("v1\t") {
        let mut fields = versioned.splitn(3, '\t');
        let kind = fields.next()?;
        let created_at_raw = fields.next()?;
        let request_and_payload = fields.next()?;
        return parse_legacy_idempotency_disk_record(kind, created_at_raw, request_and_payload);
    }
    let mut legacy = line.splitn(3, '\t');
    let kind = legacy.next()?;
    let created_at_raw = legacy.next()?;
    let request_and_payload = legacy.next()?;
    parse_legacy_idempotency_disk_record(kind, created_at_raw, request_and_payload)
}

fn parse_legacy_idempotency_disk_record(
    kind: &str,
    created_at_raw: &str,
    request_and_payload: &str,
) -> Option<(String, i64, String, String)> {
    let created_at_ms = created_at_raw.parse::<i64>().ok()?;
    let (request_id, payload_hex) = request_and_payload.rsplit_once('\t')?;
    Some((
        kind.to_string(),
        created_at_ms,
        request_id.to_string(),
        payload_hex.to_string(),
    ))
}

pub(super) fn encode_etcd_idempotency_record(created_at_ms: i64, payload: &[u8]) -> String {
    format!(
        "{IDEMPOTENCY_RECORD_SCHEMA_VERSION}\t{created_at_ms}\t{}",
        encode_hex(payload)
    )
}

pub(super) fn decode_etcd_idempotency_record(raw: &str) -> Option<(i64, Vec<u8>)> {
    let mut versioned = raw.splitn(3, '\t');
    let first = versioned.next()?;
    let second = versioned.next()?;
    let third = versioned.next();

    if first == IDEMPOTENCY_RECORD_SCHEMA_VERSION || first == IDEMPOTENCY_RECORD_SCHEMA_VERSION_V1 {
        let created_at_ms = second.parse::<i64>().ok()?;
        let payload = decode_hex(third?).ok()?;
        return Some((created_at_ms, payload));
    }

    let payload = decode_hex(second).ok()?;
    let created_at_ms = first.parse::<i64>().ok()?;
    Some((created_at_ms, payload))
}
