package com.thealgorithms.ciphers;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;

class PermutationCipherTest {

    private final PermutationCipher cipher = new PermutationCipher();

    @Test
    void testBasicEncryption() {
        // given
        String plaintext = "HELLO";
        int[] key = {3, 1, 2}; // Move 3rd position to 1st, 1st to 2nd, 2nd to 3rd

        // when
        String encrypted = cipher.encrypt(plaintext, key);

        // then
        // "HELLO" becomes "HEL" + "LOX" (padded)
        // "HEL" with key {3,1,2} becomes "LHE" (L=3rd, H=1st, E=2nd)
        // "LOX" with key {3,1,2} becomes "XLO" (X=3rd, L=1st, O=2nd)
        assertEquals("LHEXLO", encrypted);
    }

    @Test
    void testBasicDecryption() {
        // given
        String ciphertext = "LHEXLO";
        int[] key = {3, 1, 2};

        // when
        String decrypted = cipher.decrypt(ciphertext, key);

        // then
        assertEquals("HELLO", decrypted);
    }

    @Test
    void testEncryptDecryptRoundTrip() {
        // given
        String plaintext = "THIS IS A TEST MESSAGE";
        int[] key = {4, 2, 1, 3};

        // when
        String encrypted = cipher.encrypt(plaintext, key);
        String decrypted = cipher.decrypt(encrypted, key);

        // then
        assertEquals("THISISATESTMESSAGE", decrypted); // Spaces are removed during encryption
    }

    @Test
    void testSingleCharacterKey() {
        // given
        String plaintext = "ABCDEF";
        int[] key = {1}; // Identity permutation

        // when
        String encrypted = cipher.encrypt(plaintext, key);
        String decrypted = cipher.decrypt(encrypted, key);

        // then
        assertEquals("ABCDEF", encrypted); // Should remain unchanged
        assertEquals("ABCDEF", decrypted);
    }

    @Test
    void testLargerKey() {
        // given
        String plaintext = "PERMUTATION";
        int[] key = {5, 3, 1, 4, 2}; // 5-character permutation

        // when
        String encrypted = cipher.encrypt(plaintext, key);
        String decrypted = cipher.decrypt(encrypted, key);

        // then
        assertEquals("PERMUTATION", decrypted);
    }

    @Test
    void testExactBlockSize() {
        // given
        String plaintext = "ABCDEF"; // Length 6, divisible by key length 3
        int[] key = {2, 3, 1};

        // when
        String encrypted = cipher.encrypt(plaintext, key);
        String decrypted = cipher.decrypt(encrypted, key);

        // then
        assertEquals("ABCDEF", decrypted);
    }

    @Test
    void testEmptyString() {
        // given
        String plaintext = "";
        int[] key = {2, 1, 3};

        // when
        String encrypted = cipher.encrypt(plaintext, key);
        String decrypted = cipher.decrypt(encrypted, key);

        // then
        assertEquals("", encrypted);
        assertEquals("", decrypted);
    }

    @Test
    void testNullString() {
        // given
        String plaintext = null;
        int[] key = {2, 1, 3};

        // when
        String encrypted = cipher.encrypt(plaintext, key);
        String decrypted = cipher.decrypt(encrypted, key);

        // then
        assertNull(encrypted);
        assertNull(decrypted);
    }

    @Test
    void testStringWithSpaces() {
        // given
        String plaintext = "A B C D E F";
        int[] key = {2, 1};

        // when
        String encrypted = cipher.encrypt(plaintext, key);
        String decrypted = cipher.decrypt(encrypted, key);

        // then
        assertEquals("ABCDEF", decrypted); // Spaces should be removed
    }

    @Test
    void testLowercaseConversion() {
        // given
        String plaintext = "hello world";
        int[] key = {3, 1, 2};

        // when
        String encrypted = cipher.encrypt(plaintext, key);
        String decrypted = cipher.decrypt(encrypted, key);

        // then
        assertEquals("HELLOWORLD", decrypted); // Should be converted to uppercase
    }

    @Test
    void testInvalidKeyNull() {
        // given
        String plaintext = "HELLO";
        int[] key = null;

        // when & then
        assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key));
        assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key));
    }

    @Test
    void testInvalidKeyEmpty() {
        // given
        String plaintext = "HELLO";
        int[] key = {};

        // when & then
        assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key));
        assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key));
    }

    @Test
    void testInvalidKeyOutOfRange() {
        // given
        String plaintext = "HELLO";
        int[] key = {1, 2, 4}; // 4 is out of range for key length 3

        // when & then
        assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key));
        assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key));
    }

    @Test
    void testInvalidKeyZero() {
        // given
        String plaintext = "HELLO";
        int[] key = {0, 1, 2}; // 0 is invalid (should be 1-based)

        // when & then
        assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key));
        assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key));
    }

    @Test
    void testInvalidKeyDuplicate() {
        // given
        String plaintext = "HELLO";
        int[] key = {1, 2, 2}; // Duplicate position

        // when & then
        assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key));
        assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key));
    }

    @Test
    void testInvalidKeyMissingPosition() {
        // given
        String plaintext = "HELLO";
        int[] key = {1, 3}; // Missing position 2

        // when & then
        assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key));
        assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key));
    }

    @Test
    void testReverseKey() {
        // given
        String plaintext = "ABCD";
        int[] key = {4, 3, 2, 1}; // Reverse order

        // when
        String encrypted = cipher.encrypt(plaintext, key);
        String decrypted = cipher.decrypt(encrypted, key);

        // then
        assertEquals("DCBA", encrypted); // Should be reversed
        assertEquals("ABCD", decrypted);
    }

    @Test
    void testSpecificExampleFromDescription() {
        // given
        String plaintext = "HELLO";
        int[] key = {3, 1, 2};

        // when
        String encrypted = cipher.encrypt(plaintext, key);

        // then
        // Block 1: "HEL" -> positions {3,1,2} -> "LHE"
        // Block 2: "LOX" -> positions {3,1,2} -> "XLO"
        assertEquals("LHEXLO", encrypted);
        // Verify decryption
        String decrypted = cipher.decrypt(encrypted, key);
        assertEquals("HELLO", decrypted);
    }

    @Test
    void testPaddingCharacterGetter() {
        // when
        char paddingChar = cipher.getPaddingChar();

        // then
        assertEquals('X', paddingChar);
    }

    @Test
    void testLongText() {
        // given
        String plaintext = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG";
        int[] key = {4, 1, 3, 2};

        // when
        String encrypted = cipher.encrypt(plaintext, key);
        String decrypted = cipher.decrypt(encrypted, key);

        // then
        assertEquals("THEQUICKBROWNFOXJUMPSOVERTHELAZYDOG", decrypted);
    }

    @Test
    void testIdentityPermutation() {
        // given
        String plaintext = "IDENTITY";
        int[] key = {1, 2, 3, 4}; // Identity permutation

        // when
        String encrypted = cipher.encrypt(plaintext, key);
        String decrypted = cipher.decrypt(encrypted, key);

        // then
        assertEquals("IDENTITY", encrypted); // Should remain unchanged
        assertEquals("IDENTITY", decrypted);
    }

    @Test
    void testEmptyStringRemovePadding() {
        // given - Test to cover line 178 (empty string case in removePadding)
        String ciphertext = "";
        int[] key = {2, 1, 3};

        // when
        String decrypted = cipher.decrypt(ciphertext, key);

        // then
        assertEquals("", decrypted); // Should return empty string directly
    }

    @Test
    void testBlockShorterThanKey() {
        // given - Test to cover line 139 (block length != key length case)
        // This is a defensive case where permuteBlock might receive a block shorter than key
        // We can test this by manually creating a scenario with malformed ciphertext
        String malformedCiphertext = "AB"; // Length 2, but key length is 3
        int[] key = {3, 1, 2}; // Key length is 3

        // when - This should trigger the padding logic in permuteBlock during decryption
        String decrypted = cipher.decrypt(malformedCiphertext, key);

        // then - The method should handle the short block gracefully
        // "AB" gets padded to "ABX", then permuted with inverse key {2,3,1}
        // inverse key {2,3,1} means: pos 2→1st, pos 3→2nd, pos 1→3rd = "BXA"
        // Padding removal only removes trailing X's, so "BXA" remains as is
        assertEquals("BXA", decrypted);
    }
}
