package protocol

import (
	"crypto/aes"
	"crypto/cipher"
	"errors"
)

// OMAC1 (CMAC) Implementation for AES-128
// RFC 4493

func shiftLeft(in []byte, out []byte) {
	var carry byte
	for i := 15; i >= 0; i-- {
		b := in[i]
		out[i] = (b << 1) | carry
		carry = (b >> 7) & 1
	}
}

func xorBlock(a, b []byte) {
	for i := 0; i < 16; i++ {
		a[i] ^= b[i]
	}
}

func generateSubkeys(cipherBlock cipher.Block) (k1, k2 []byte) {
	l := make([]byte, 16)
	cipherBlock.Encrypt(l, make([]byte, 16))

	k1 = make([]byte, 16)
	shiftLeft(l, k1)
	if l[0]&0x80 != 0 {
		k1[15] ^= 0x87
	}

	k2 = make([]byte, 16)
	shiftLeft(k1, k2)
	if k1[0]&0x80 != 0 {
		k2[15] ^= 0x87
	}
	return
}

func omac1(cipherBlock cipher.Block, data []byte) []byte {
	k1, k2 := generateSubkeys(cipherBlock)

	n := len(data)
	numBlocks := (n + 15) / 16
	if numBlocks == 0 {
		numBlocks = 1
	}

	lastBlock := make([]byte, 16)
	flagComplete := (n > 0 && n%16 == 0)

	if flagComplete {
		copy(lastBlock, data[n-16:])
		xorBlock(lastBlock, k1)
	} else {
		remaining := n % 16
		copy(lastBlock, data[n-remaining:])
		lastBlock[remaining] = 0x80 // padding
		xorBlock(lastBlock, k2)
	}

	x := make([]byte, 16)
	y := make([]byte, 16)

	for i := 0; i < numBlocks-1; i++ {
		copy(y, data[i*16:(i+1)*16])
		xorBlock(y, x)
		cipherBlock.Encrypt(x, y)
	}

	xorBlock(x, lastBlock)
	cipherBlock.Encrypt(x, x)
	return x
}

// EAX implementation functions

// omacWithTag computes OMAC(t || m)
func omacWithTag(block cipher.Block, tag byte, m []byte) []byte {
	// EAX uses [t] as prefix for OMAC
	// but standard OMAC implies it's just the message.
	// EAX spec: OMAC^t_K(M) = OMAC_K ([t]_n || M)
	// where n is block size (16).

	// We construct a new slice with the prefix
	buf := make([]byte, 16+len(m))
	buf[15] = tag // The last byte of the first block is the tag?
	// Wait, EAX spec: [t]_n is the byte t padded with zeros to block size n.
	// Usually it implies the last byte is t, or first?
	// "Let [t]_n denote the n-byte string that consists of n-1 zero bytes followed by the byte t."
	// So for n=16, 15 zeros then t.

	copy(buf[16:], m)
	return omac1(block, buf)
}

// EncryptEAX performs EAX encryption and returns ciphertext + mac
// Key and Nonce must be valid for AES-128 (16 bytes).
func EncryptEAX(key, nonce, header, data []byte) (ciphertext []byte, mac []byte, err error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, nil, err
	}

	// 1. Calculate Nonce MAC (N)
	// N = OMAC^0_K(Nonce)
	nMac := omacWithTag(block, 0, nonce)

	// 2. Calculate Header MAC (H)
	// H = OMAC^1_K(Header)
	hMac := omacWithTag(block, 1, header)

	// 3. Encrypt Data (C)
	// CTR using N as counter/IV
	ciphertext = make([]byte, len(data))
	ctrStream := cipher.NewCTR(block, nMac)
	ctrStream.XORKeyStream(ciphertext, data)

	// 4. Calculate Ciphertext MAC (C_MAC)
	// C_MAC = OMAC^2_K(Ciphertext)
	cMac := omacWithTag(block, 2, ciphertext)

	// 5. Final Tag = N ^ H ^ C_MAC
	tag := make([]byte, 16)
	for i := 0; i < 16; i++ {
		tag[i] = nMac[i] ^ hMac[i] ^ cMac[i]
	}

	// TS3 uses 8 bytes of the MAC
	return ciphertext, tag[:8], nil
}

// DecryptEAX verifies and decrypts
func DecryptEAX(key, nonce, header, data, expectedMac []byte) (plaintext []byte, err error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	// 1. N = OMAC^0_K(Nonce)
	nMac := omacWithTag(block, 0, nonce)

	// 2. H = OMAC^1_K(Header)
	hMac := omacWithTag(block, 1, header)

	// 3. C_MAC = OMAC^2_K(Data is Ciphertext here)
	cMac := omacWithTag(block, 2, data)

	// 4. Calculate Tag
	tag := make([]byte, 16)
	for i := 0; i < 16; i++ {
		tag[i] = nMac[i] ^ hMac[i] ^ cMac[i]
	}

	// Verify MAC
	if len(expectedMac) > 16 {
		return nil, errors.New("expected MAC too long")
	}

	// Constant time compare needed ideally
	for i := 0; i < len(expectedMac); i++ {
		if tag[i] != expectedMac[i] {
			return nil, errors.New("mac verification failed")
		}
	}

	// 5. Decrypt
	plaintext = make([]byte, len(data))
	ctrStream := cipher.NewCTR(block, nMac)
	ctrStream.XORKeyStream(plaintext, data)

	return plaintext, nil
}
