import * as bs58 from 'bs58';
import { describe, expect, it } from 'vitest';

import type { NormalizedHexString } from '@layerzerolabs/common-chain-model';
import { normalizeHex } from '@layerzerolabs/common-chain-model';
import { bytesToHexPrefixed, hexToBytes, hexZeroPad } from '@layerzerolabs/common-utils';
import { ChainName } from '@layerzerolabs/layerzero-definitions';

import { addressParser } from './addressParser';

// Test data generators
const randomBytes = (length: number): Uint8Array => {
    const bytes = new Uint8Array(length);
    for (let i = 0; i < length; i++) {
        bytes[i] = Math.floor(Math.random() * 256);
    }
    return bytes;
};

const randomHex = (byteLength: number): NormalizedHexString => {
    return normalizeHex(bytesToHexPrefixed(randomBytes(byteLength)));
};

const runPropertyTest = <T>(fn: () => T, iterations = 100): void => {
    for (let i = 0; i < iterations; i++) {
        fn();
    }
};

describe('addressParser - hex/EVM chains', () => {
    const evmChains = [ChainName.ETHEREUM, ChainName.AVALANCHE, ChainName.BSC, ChainName.POLYGON];

    evmChains.forEach((chain) => {
        describe(chain, () => {
            const parser = addressParser(chain);

            it('maintains roundtrip invariant: hex -> native -> hex', () => {
                runPropertyTest(() => {
                    const input = randomHex(20);
                    const native = parser.normalizedToNative(input);
                    const back = parser.nativeToNormalized(native);
                    expect(back).toBe(input);
                });
            });

            it('maintains byte representation consistency', () => {
                runPropertyTest(() => {
                    const input = randomHex(20);
                    const nativeStr = parser.normalizedToNativeString(input);
                    const bytesFromHex = hexToBytes(nativeStr);
                    const nativeBytes = parser.nativeToBytes(nativeStr);

                    expect(nativeBytes).toEqual(bytesFromHex);
                });
            });

            it('correctly pads addresses', () => {
                runPropertyTest(() => {
                    const byteLength = Math.floor(Math.random() * 20) + 1; // 1-20 bytes
                    const input = randomHex(byteLength);
                    const native = parser.normalizedToNative(input);
                    expect(native.nativeAddress.length).toBe(42); // Always 20 bytes padded
                    expect(native.chainName).toBe(chain);
                });
            });
        });
    });

    it('normalizedToBytes32Hex pads correctly', () => {
        runPropertyTest(() => {
            const byteLength = Math.floor(Math.random() * 32) + 1; // 1-32 bytes
            const input = randomHex(byteLength);
            const padded = addressParser.normalizedToBytes32Hex(input);
            expect(padded.length).toBe(66); // 32 bytes -> 64 hex chars + 0x
            expect(padded).toBe(hexZeroPad(input as any, 32));
        });
    });

    it('handles edge cases', () => {
        const parser = addressParser(ChainName.ETHEREUM);

        // Empty address
        const empty = normalizeHex('0x');
        expect(parser.normalizedToNative(empty).nativeAddress.length).toBe(42);

        // Maximum value
        const max = normalizeHex(('0x' + 'ff'.repeat(20)) as any);
        expect(parser.nativeToNormalized(parser.normalizedToNative(max))).toBe(max);

        // Single byte
        const single = normalizeHex('0x01');
        expect(parser.normalizedToNative(single).nativeAddress).toBe(hexZeroPad('0x01', 20));
    });
});

describe('addressParser - base58 chains (Solana)', () => {
    const parser = addressParser(ChainName.SOLANA);

    it('maintains roundtrip invariant: hex -> base58 -> hex', () => {
        runPropertyTest(() => {
            const byteLength = Math.floor(Math.random() * 32) + 1; // 1-32 bytes
            const input = randomHex(byteLength);
            const native = parser.normalizedToNative(input);
            const back = parser.nativeToNormalized(native);
            expect(back).toBe(input);
        });
    });

    it('maintains base58 encoding consistency', () => {
        runPropertyTest(() => {
            const input = randomHex(32);
            const nativeStr = parser.normalizedToNativeString(input);
            const expectedBase58 = bs58.encode(hexToBytes(input as any));
            expect(nativeStr).toBe(expectedBase58);

            // Verify bytes consistency
            const nativeBytes = parser.nativeToBytes(nativeStr);
            expect(nativeBytes).toEqual(bs58.decode(expectedBase58));
        });
    });

    it('correctly handles 32-byte padding', () => {
        runPropertyTest(() => {
            const byteLength = Math.floor(Math.random() * 32) + 1;
            const input = randomHex(byteLength);
            const native = parser.normalizedToNativeString(input);
            const padded = parser.nativeToBytes32Hex(native);
            expect(padded.length).toBe(66);

            // Verify padding preserves original value
            expect(parser.nativeToNormalized(native)).toBe(
                normalizeHex(bytesToHexPrefixed(hexToBytes(input as any))),
            );
        });
    });

    it('handles edge cases', () => {
        // Single byte
        const single = normalizeHex('0x01');
        const singleNative = parser.normalizedToNative(single);
        expect(parser.nativeToNormalized(singleNative)).toBe(single);

        // Empty (though typically invalid for Solana)
        const empty = normalizeHex('0x');
        expect(() => parser.normalizedToNative(empty)).not.toThrow();

        // 32 bytes (typical Solana address length)
        const full32 = randomHex(32);
        const native32 = parser.normalizedToNative(full32);
        expect(parser.nativeToNormalized(native32)).toBe(full32);
    });
});

describe('addressParser - chain validation', () => {
    it('validates chain names for nativeToBytes32Hex', () => {
        const evmParser = addressParser(ChainName.ETHEREUM);
        const solanaParser = addressParser(ChainName.SOLANA);

        const evmAddress = evmParser.normalizedToNative(randomHex(20));
        const solanaAddress = solanaParser.normalizedToNative(randomHex(32));

        // nativeTo32BytesHex validates chain names for NativeAddress objects
        expect(() => solanaParser.nativeToBytes32Hex(evmAddress as any)).toThrow(/Cannot convert/);
        expect(() => evmParser.nativeToBytes32Hex(solanaAddress as any)).toThrow(/Cannot convert/);
    });

    it('accepts string addresses without validation', () => {
        const parser = addressParser(ChainName.ETHEREUM);
        const randomAddress = '0x' + 'ab'.repeat(20);

        // String addresses bypass chain validation for all methods
        const result = parser.nativeToNormalized(randomAddress as any);
        expect(result).toBe(normalizeHex(randomAddress as any));

        const padded = parser.nativeToBytes32Hex(randomAddress as any);
        expect(padded.length).toBe(66);

        const bytes = parser.nativeToBytes(randomAddress as any);
        expect(bytes).toEqual(hexToBytes(randomAddress));
    });
});

describe('addressParser - TON', () => {
    const parser = addressParser(ChainName.TON);

    it('maintains roundtrip invariant: hex -> ton address -> hex', () => {
        runPropertyTest(() => {
            // TON addresses are typically 32 bytes (256 bits)
            const byteLength = Math.floor(Math.random() * 32) + 1;
            const input = randomHex(byteLength);
            const native = parser.normalizedToNative(input);
            const back = parser.nativeToNormalized(native);

            expect(input).toBe(back);
        });
    });

    it('correctly handles TON address format', () => {
        runPropertyTest(() => {
            const input = randomHex(32);
            const native = parser.normalizedToNative(input);

            // TON addresses should have the correct chain name
            expect(native.chainName).toBe(ChainName.TON);

            // Should be a valid TON address string
            expect(typeof native.nativeAddress).toBe('string');
            expect(native.nativeAddress.length).toBeGreaterThan(0);
        });
    });

    it('maintains byte representation consistency', () => {
        runPropertyTest(() => {
            const input = randomHex(32);
            const nativeStr = parser.normalizedToNativeString(input);
            const nativeBytes = parser.nativeToBytes(nativeStr);

            // Both should produce valid byte arrays
            expect(nativeBytes).toBeInstanceOf(Uint8Array);

            // Converting back should preserve the value
            const backFromBytes = parser.nativeToNormalized(parser.normalizedToNative(input));
            expect(backFromBytes).toBe(input);
        });
    });

    it('correctly handles 32-byte padding', () => {
        runPropertyTest(() => {
            const byteLength = Math.floor(Math.random() * 32) + 1;
            const input = randomHex(byteLength);
            const native = parser.normalizedToNativeString(input);
            const padded = parser.nativeToBytes32Hex(native);

            expect(padded.length).toBe(66); // 32 bytes -> 64 hex chars + 0x

            // Verify the numeric value is preserved
            const paddedBigInt = BigInt(padded);
            const inputBigInt = BigInt(input as any);
            expect(paddedBigInt).toBe(inputBigInt);
        });
    });

    it('handles edge cases', () => {
        // Zero address
        const zero = normalizeHex('0x0');
        const zeroNative = parser.normalizedToNative(zero);
        expect(() => parser.nativeToNormalized(zeroNative)).not.toThrow();

        // Large values
        const large = normalizeHex(('0x' + 'ff'.repeat(32)) as any);
        const largeNative = parser.normalizedToNative(large);
        const largeBack = parser.nativeToNormalized(largeNative);
        expect(BigInt(largeBack as any)).toBe(BigInt(large as any));

        // Single byte
        const single = normalizeHex('0x01');
        const singleNative = parser.normalizedToNative(single);
        const singleBack = parser.nativeToNormalized(singleNative);
        expect(BigInt(singleBack as any)).toBe(1n);
    });

    it('validates native addresses', () => {
        const validAddress = parser.normalizedToNative(randomHex(32));
        expect(parser.validateNative(validAddress.nativeAddress)).toBe(true);

        // Invalid addresses should return false
        expect(parser.validateNative('invalid-ton-address')).toBe(false);
    });
});
