package crypt

import (
	"bytes"
	"testing"

	"forge.lthn.ai/core/gui/pkg/core"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// --- Constructor Tests ---

func TestNew(t *testing.T) {
	t.Run("creates service successfully", func(t *testing.T) {
		service, err := New()
		assert.NoError(t, err)
		assert.NotNil(t, service)
	})

	t.Run("returns independent instances", func(t *testing.T) {
		service1, err1 := New()
		service2, err2 := New()
		assert.NoError(t, err1)
		assert.NoError(t, err2)
		assert.NotSame(t, service1, service2)
	})
}

func TestRegister(t *testing.T) {
	t.Run("registers with core successfully", func(t *testing.T) {
		coreInstance, err := core.New()
		require.NoError(t, err)

		service, err := Register(coreInstance)
		require.NoError(t, err)
		assert.NotNil(t, service)
	})

	t.Run("returns Service type with ServiceRuntime", func(t *testing.T) {
		coreInstance, err := core.New()
		require.NoError(t, err)

		service, err := Register(coreInstance)
		require.NoError(t, err)

		cryptService, ok := service.(*Service)
		assert.True(t, ok)
		assert.NotNil(t, cryptService.ServiceRuntime)
	})
}

// --- Hash Tests ---

func TestHash(t *testing.T) {
	s, _ := New()

	t.Run("LTHN hash", func(t *testing.T) {
		hash := s.Hash(LTHN, "hello")
		assert.NotEmpty(t, hash)
		// LTHN hash should be consistent
		hash2 := s.Hash(LTHN, "hello")
		assert.Equal(t, hash, hash2)
	})

	t.Run("SHA512 hash", func(t *testing.T) {
		hash := s.Hash(SHA512, "hello")
		// Known SHA512 hash for "hello"
		expected := "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
		assert.Equal(t, expected, hash)
	})

	t.Run("SHA256 hash", func(t *testing.T) {
		hash := s.Hash(SHA256, "hello")
		// Known SHA256 hash for "hello"
		expected := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
		assert.Equal(t, expected, hash)
	})

	t.Run("SHA1 hash", func(t *testing.T) {
		hash := s.Hash(SHA1, "hello")
		// Known SHA1 hash for "hello"
		expected := "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"
		assert.Equal(t, expected, hash)
	})

	t.Run("MD5 hash", func(t *testing.T) {
		hash := s.Hash(MD5, "hello")
		// Known MD5 hash for "hello"
		expected := "5d41402abc4b2a76b9719d911017c592"
		assert.Equal(t, expected, hash)
	})

	t.Run("default falls back to SHA256", func(t *testing.T) {
		hash := s.Hash("unknown", "hello")
		sha256Hash := s.Hash(SHA256, "hello")
		assert.Equal(t, sha256Hash, hash)
	})

	t.Run("empty string hash", func(t *testing.T) {
		hash := s.Hash(SHA256, "")
		// Known SHA256 hash for empty string
		expected := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
		assert.Equal(t, expected, hash)
	})

	t.Run("hash with special characters", func(t *testing.T) {
		hash := s.Hash(SHA256, "hello!@#$%^&*()")
		assert.NotEmpty(t, hash)
		assert.Len(t, hash, 64) // SHA256 produces 64 hex chars
	})

	t.Run("hash with unicode", func(t *testing.T) {
		hash := s.Hash(SHA256, "你好世界")
		assert.NotEmpty(t, hash)
		assert.Len(t, hash, 64)
	})

	t.Run("hash consistency", func(t *testing.T) {
		payload := "test payload for consistency check"
		for _, hashType := range []HashType{LTHN, SHA512, SHA256, SHA1, MD5} {
			hash1 := s.Hash(hashType, payload)
			hash2 := s.Hash(hashType, payload)
			assert.Equal(t, hash1, hash2, "Hash should be consistent for %s", hashType)
		}
	})
}

// --- Luhn Tests ---

func TestLuhn(t *testing.T) {
	s, _ := New()

	t.Run("valid Luhn numbers", func(t *testing.T) {
		validNumbers := []string{
			"79927398713",
			"4532015112830366", // Visa test number
			"6011514433546201", // Discover test number
			"371449635398431",  // Amex test number
			"30569309025904",   // Diners Club test number
		}
		for _, num := range validNumbers {
			assert.True(t, s.Luhn(num), "Expected %s to be valid", num)
		}
	})

	t.Run("invalid Luhn numbers", func(t *testing.T) {
		invalidNumbers := []string{
			"79927398714",
			"1234567890",
			"1111111111",
			"1234567891",
		}
		for _, num := range invalidNumbers {
			assert.False(t, s.Luhn(num), "Expected %s to be invalid", num)
		}
	})

	t.Run("all zeros is valid", func(t *testing.T) {
		// All zeros: each digit contributes 0, sum=0, 0%10==0
		assert.True(t, s.Luhn("0000000000"))
	})

	t.Run("handles spaces", func(t *testing.T) {
		// Same number with and without spaces should give same result
		assert.True(t, s.Luhn("7992 7398 713"))
		assert.True(t, s.Luhn("4532 0151 1283 0366"))
	})

	t.Run("non-digit characters return false", func(t *testing.T) {
		assert.False(t, s.Luhn("1234abcd5678"))
		assert.False(t, s.Luhn("12-34-56-78"))
		assert.False(t, s.Luhn("1234.5678"))
	})

	t.Run("empty string", func(t *testing.T) {
		// Enchantrix treats empty string as invalid
		assert.False(t, s.Luhn(""))
	})

	t.Run("single digit", func(t *testing.T) {
		// Enchantrix requires minimum length for valid Luhn
		assert.False(t, s.Luhn("0"))
		assert.False(t, s.Luhn("1"))
	})
}

// --- Fletcher Checksum Tests ---

func TestFletcher16(t *testing.T) {
	s, _ := New()

	t.Run("basic checksum", func(t *testing.T) {
		checksum := s.Fletcher16("hello")
		assert.NotZero(t, checksum)
	})

	t.Run("empty string", func(t *testing.T) {
		checksum := s.Fletcher16("")
		assert.Equal(t, uint16(0), checksum)
	})

	t.Run("consistency", func(t *testing.T) {
		checksum1 := s.Fletcher16("test data")
		checksum2 := s.Fletcher16("test data")
		assert.Equal(t, checksum1, checksum2)
	})

	t.Run("different inputs produce different checksums", func(t *testing.T) {
		checksum1 := s.Fletcher16("hello")
		checksum2 := s.Fletcher16("world")
		assert.NotEqual(t, checksum1, checksum2)
	})

	t.Run("known value", func(t *testing.T) {
		// "abcde" has a known Fletcher-16 checksum
		checksum := s.Fletcher16("abcde")
		assert.NotZero(t, checksum)
	})
}

func TestFletcher32(t *testing.T) {
	s, _ := New()

	t.Run("basic checksum", func(t *testing.T) {
		checksum := s.Fletcher32("hello")
		assert.NotZero(t, checksum)
	})

	t.Run("empty string", func(t *testing.T) {
		checksum := s.Fletcher32("")
		assert.Equal(t, uint32(0), checksum)
	})

	t.Run("consistency", func(t *testing.T) {
		checksum1 := s.Fletcher32("test data")
		checksum2 := s.Fletcher32("test data")
		assert.Equal(t, checksum1, checksum2)
	})

	t.Run("different inputs produce different checksums", func(t *testing.T) {
		checksum1 := s.Fletcher32("hello")
		checksum2 := s.Fletcher32("world")
		assert.NotEqual(t, checksum1, checksum2)
	})

	t.Run("handles odd-length input", func(t *testing.T) {
		// Odd length input should be padded
		checksum := s.Fletcher32("abc")
		assert.NotZero(t, checksum)
	})

	t.Run("handles even-length input", func(t *testing.T) {
		checksum := s.Fletcher32("abcd")
		assert.NotZero(t, checksum)
	})
}

func TestFletcher64(t *testing.T) {
	s, _ := New()

	t.Run("basic checksum", func(t *testing.T) {
		checksum := s.Fletcher64("hello")
		assert.NotZero(t, checksum)
	})

	t.Run("empty string", func(t *testing.T) {
		checksum := s.Fletcher64("")
		assert.Equal(t, uint64(0), checksum)
	})

	t.Run("consistency", func(t *testing.T) {
		checksum1 := s.Fletcher64("test data")
		checksum2 := s.Fletcher64("test data")
		assert.Equal(t, checksum1, checksum2)
	})

	t.Run("different inputs produce different checksums", func(t *testing.T) {
		checksum1 := s.Fletcher64("hello")
		checksum2 := s.Fletcher64("world")
		assert.NotEqual(t, checksum1, checksum2)
	})

	t.Run("handles various input lengths", func(t *testing.T) {
		// Test padding for different lengths
		for i := 1; i <= 8; i++ {
			input := string(make([]byte, i))
			checksum := s.Fletcher64(input)
			// Just verify it doesn't panic
			_ = checksum
		}
	})

	t.Run("long input", func(t *testing.T) {
		// Use actual text content, not null bytes
		longInput := ""
		for i := 0; i < 100; i++ {
			longInput += "test data "
		}
		checksum := s.Fletcher64(longInput)
		assert.NotZero(t, checksum)
	})
}

// --- HashType Constants Tests ---

func TestHashTypeConstants(t *testing.T) {
	t.Run("constants have expected values", func(t *testing.T) {
		assert.Equal(t, HashType("lthn"), LTHN)
		assert.Equal(t, HashType("sha512"), SHA512)
		assert.Equal(t, HashType("sha256"), SHA256)
		assert.Equal(t, HashType("sha1"), SHA1)
		assert.Equal(t, HashType("md5"), MD5)
	})
}

// --- IsHashAlgo Tests ---

func TestIsHashAlgo(t *testing.T) {
	s, _ := New()

	t.Run("valid hash algorithms", func(t *testing.T) {
		assert.True(t, s.IsHashAlgo("sha256"))
		assert.True(t, s.IsHashAlgo("sha512"))
		assert.True(t, s.IsHashAlgo("sha1"))
		assert.True(t, s.IsHashAlgo("md5"))
	})

	t.Run("invalid hash algorithm", func(t *testing.T) {
		assert.False(t, s.IsHashAlgo("invalid"))
		assert.False(t, s.IsHashAlgo(""))
	})
}

// --- RSA Tests ---

func TestGenerateRSAKeyPair(t *testing.T) {
	s, _ := New()

	t.Run("generates valid key pair", func(t *testing.T) {
		pubKey, privKey, err := s.GenerateRSAKeyPair(2048)
		require.NoError(t, err)
		assert.NotEmpty(t, pubKey)
		assert.NotEmpty(t, privKey)
		assert.Contains(t, pubKey, "PUBLIC KEY")
		assert.Contains(t, privKey, "PRIVATE KEY")
	})
}

func TestEncryptDecryptRSA(t *testing.T) {
	s, _ := New()

	t.Run("encrypt and decrypt roundtrip", func(t *testing.T) {
		pubKey, privKey, err := s.GenerateRSAKeyPair(2048)
		require.NoError(t, err)

		plaintext := "hello RSA world"
		ciphertext, err := s.EncryptRSA(pubKey, plaintext)
		require.NoError(t, err)
		assert.NotEmpty(t, ciphertext)
		assert.NotEqual(t, plaintext, ciphertext)

		decrypted, err := s.DecryptRSA(privKey, ciphertext)
		require.NoError(t, err)
		assert.Equal(t, plaintext, decrypted)
	})

	t.Run("encrypt with invalid key fails", func(t *testing.T) {
		_, err := s.EncryptRSA("invalid key", "data")
		assert.Error(t, err)
	})

	t.Run("decrypt with invalid key fails", func(t *testing.T) {
		_, err := s.DecryptRSA("invalid key", "data")
		assert.Error(t, err)
	})
}

// --- PGP Tests ---

func TestGeneratePGPKeyPair(t *testing.T) {
	s, _ := New()

	t.Run("generates valid key pair", func(t *testing.T) {
		pubKey, privKey, err := s.GeneratePGPKeyPair("Test User", "test@example.com", "test comment")
		require.NoError(t, err)
		assert.NotEmpty(t, pubKey)
		assert.NotEmpty(t, privKey)
		assert.Contains(t, pubKey, "PGP PUBLIC KEY")
		assert.Contains(t, privKey, "PGP PRIVATE KEY")
	})
}

func TestEncryptPGP(t *testing.T) {
	s, _ := New()

	t.Run("requires valid key", func(t *testing.T) {
		var buf bytes.Buffer
		err := s.EncryptPGP(&buf, "invalid key content", "test data")
		assert.Error(t, err)
	})

	t.Run("encrypts with valid key", func(t *testing.T) {
		pubKey, _, err := s.GeneratePGPKeyPair("Test", "test@test.com", "comment")
		require.NoError(t, err)

		var buf bytes.Buffer
		err = s.EncryptPGP(&buf, pubKey, "test data")
		require.NoError(t, err)
		assert.NotEmpty(t, buf.String())
	})
}

func TestEncryptPGPToString(t *testing.T) {
	s, _ := New()

	t.Run("encrypts to string", func(t *testing.T) {
		pubKey, _, err := s.GeneratePGPKeyPair("Test", "test@test.com", "comment")
		require.NoError(t, err)

		ciphertext, err := s.EncryptPGPToString(pubKey, "test data")
		require.NoError(t, err)
		assert.NotEmpty(t, ciphertext)
	})

	t.Run("requires valid key", func(t *testing.T) {
		_, err := s.EncryptPGPToString("invalid key", "data")
		assert.Error(t, err)
	})
}

func TestDecryptPGP(t *testing.T) {
	s, _ := New()

	t.Run("requires valid key", func(t *testing.T) {
		_, err := s.DecryptPGP("invalid key content", "encrypted data")
		assert.Error(t, err)
	})

	t.Run("decrypts with valid key", func(t *testing.T) {
		pubKey, privKey, err := s.GeneratePGPKeyPair("Test", "test@test.com", "comment")
		require.NoError(t, err)

		plaintext := "secret message"
		ciphertext, err := s.EncryptPGPToString(pubKey, plaintext)
		require.NoError(t, err)

		decrypted, err := s.DecryptPGP(privKey, ciphertext)
		require.NoError(t, err)
		assert.Equal(t, plaintext, decrypted)
	})
}

func TestSignAndVerifyPGP(t *testing.T) {
	s, _ := New()

	t.Run("sign and verify roundtrip", func(t *testing.T) {
		pubKey, privKey, err := s.GeneratePGPKeyPair("Test", "test@test.com", "comment")
		require.NoError(t, err)

		data := "data to sign"
		signature, err := s.SignPGP(privKey, data)
		require.NoError(t, err)
		assert.NotEmpty(t, signature)

		err = s.VerifyPGP(pubKey, data, signature)
		assert.NoError(t, err)
	})

	t.Run("sign with invalid key fails", func(t *testing.T) {
		_, err := s.SignPGP("invalid key", "data")
		assert.Error(t, err)
	})

	t.Run("verify with invalid key fails", func(t *testing.T) {
		err := s.VerifyPGP("invalid key", "data", "signature")
		assert.Error(t, err)
	})
}

func TestSymmetricallyEncryptPGP(t *testing.T) {
	s, _ := New()

	t.Run("encrypts with passphrase", func(t *testing.T) {
		var buf bytes.Buffer
		err := s.SymmetricallyEncryptPGP(&buf, "secret data", "my passphrase")
		require.NoError(t, err)
		assert.NotEmpty(t, buf.String())
	})
}

// --- HandleIPCEvents Tests ---

func TestHandleIPCEvents(t *testing.T) {
	t.Run("handles ActionServiceStartup", func(t *testing.T) {
		coreInstance, err := core.New()
		require.NoError(t, err)

		serviceAny, err := Register(coreInstance)
		require.NoError(t, err)

		s := serviceAny.(*Service)
		err = s.HandleIPCEvents(coreInstance, core.ActionServiceStartup{})
		assert.NoError(t, err)
	})

	t.Run("handles unknown message type", func(t *testing.T) {
		coreInstance, err := core.New()
		require.NoError(t, err)

		serviceAny, err := Register(coreInstance)
		require.NoError(t, err)

		s := serviceAny.(*Service)
		// Pass an arbitrary type as unknown message
		err = s.HandleIPCEvents(coreInstance, "unknown message")
		assert.NoError(t, err)
	})
}
