const BLOCK_SIZE: usize = 64;
const DIGEST_SIZE: usize = 20;

/// Computes a SHA-1 digest of `bytes` using a pure-Rust implementation.
///
/// Used exclusively to produce the region and self-hash fields required by the `SqPack`
/// index header format. No external crypto dependency is needed because correctness is
/// verified by the known-vector tests below.
#[allow(clippy::many_single_char_names)]
pub(crate) fn sha1(bytes: &[u8]) -> [u8; DIGEST_SIZE] {
    let mut state: [u32; 5] = [
        0x6745_2301,
        0xEFCD_AB89,
        0x98BA_DCFE,
        0x1032_5476,
        0xC3D2_E1F0,
    ];

    let full_blocks = bytes.len() / BLOCK_SIZE;
    for i in 0..full_blocks {
        let chunk = &bytes[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE];
        process_block(&mut state, chunk);
    }

    let tail = &bytes[full_blocks * BLOCK_SIZE..];
    let mut buf = [0u8; BLOCK_SIZE * 2];
    buf[..tail.len()].copy_from_slice(tail);
    buf[tail.len()] = 0x80;
    let len_offset = if tail.len() < 56 { 56 } else { 56 + BLOCK_SIZE };
    let final_blocks = if tail.len() < 56 { 1 } else { 2 };
    let bit_length = (bytes.len() as u64) * 8;
    buf[len_offset..len_offset + 8].copy_from_slice(&bit_length.to_be_bytes());
    for i in 0..final_blocks {
        let chunk = &buf[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE];
        process_block(&mut state, chunk);
    }

    let mut out = [0u8; DIGEST_SIZE];
    for (i, word) in state.iter().enumerate() {
        out[i * 4..i * 4 + 4].copy_from_slice(&word.to_be_bytes());
    }
    out
}

#[allow(clippy::many_single_char_names)]
fn process_block(state: &mut [u32; 5], block: &[u8]) {
    debug_assert_eq!(block.len(), BLOCK_SIZE);
    let mut w = [0u32; 80];
    for i in 0..16 {
        w[i] = u32::from_be_bytes([
            block[i * 4],
            block[i * 4 + 1],
            block[i * 4 + 2],
            block[i * 4 + 3],
        ]);
    }
    for i in 16..80 {
        w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1);
    }

    let (mut a, mut b, mut c, mut d, mut e) = (state[0], state[1], state[2], state[3], state[4]);

    for (i, word) in w.iter().enumerate() {
        let (f, k) = if i < 20 {
            ((b & c) | (!b & d), 0x5A82_7999_u32)
        } else if i < 40 {
            (b ^ c ^ d, 0x6ED9_EBA1_u32)
        } else if i < 60 {
            ((b & c) | (b & d) | (c & d), 0x8F1B_BCDC_u32)
        } else {
            (b ^ c ^ d, 0xCA62_C1D6_u32)
        };

        let temp = a
            .rotate_left(5)
            .wrapping_add(f)
            .wrapping_add(e)
            .wrapping_add(k)
            .wrapping_add(*word);
        e = d;
        d = c;
        c = b.rotate_left(30);
        b = a;
        a = temp;
    }

    state[0] = state[0].wrapping_add(a);
    state[1] = state[1].wrapping_add(b);
    state[2] = state[2].wrapping_add(c);
    state[3] = state[3].wrapping_add(d);
    state[4] = state[4].wrapping_add(e);
}

/// Zero-pads a 20-byte SHA-1 digest to the 64-byte slot reserved in `SqPack` index headers.
pub(crate) fn pad_to_64(digest: [u8; DIGEST_SIZE]) -> [u8; 64] {
    let mut out = [0u8; 64];
    out[..DIGEST_SIZE].copy_from_slice(&digest);
    out
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn sha1_known_vectors() {
        let d = sha1(b"abc");
        let expected = [
            0xa9, 0x99, 0x3e, 0x36, 0x47, 0x06, 0x81, 0x6a, 0xba, 0x3e, 0x25, 0x71, 0x78, 0x50,
            0xc2, 0x6c, 0x9c, 0xd0, 0xd8, 0x9d,
        ];
        assert_eq!(d, expected);
    }

    #[test]
    fn sha1_empty() {
        let d = sha1(b"");
        let expected = [
            0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60,
            0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
        ];
        assert_eq!(d, expected);
    }

    #[test]
    fn sha1_long_message_spans_multiple_blocks() {
        let input = vec![b'a'; 1_000_000];
        let d = sha1(&input);
        let expected = [
            0x34, 0xaa, 0x97, 0x3c, 0xd4, 0xc4, 0xda, 0xa4, 0xf6, 0x1e, 0xeb, 0x2b, 0xdb, 0xad,
            0x27, 0x31, 0x65, 0x34, 0x01, 0x6f,
        ];
        assert_eq!(d, expected);
    }

    #[test]
    fn sha1_two_blocks_exact() {
        let input = b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
        let d = sha1(input);
        let expected = [
            0x84, 0x98, 0x3e, 0x44, 0x1c, 0x3b, 0xd2, 0x6e, 0xba, 0xae, 0x4a, 0xa1, 0xf9, 0x51,
            0x29, 0xe5, 0xe5, 0x46, 0x70, 0xf1,
        ];
        assert_eq!(d, expected);
    }

    #[test]
    fn pad_to_64_zero_fills_tail() {
        let digest = [0xABu8; 20];
        let padded = pad_to_64(digest);
        assert_eq!(&padded[..20], &[0xAB; 20]);
        assert_eq!(&padded[20..], &[0u8; 44]);
    }
}
