import { describe, test, expect, beforeEach, vi } from 'vitest'
import { HDPrivateKey, Networks } from 'libnexa-ts'
import AccountStore from '../../../src/wallet/accounts/AccountStore'
import { AccountType } from '../../../src/utils/WalletUtils'
import { BaseAccount } from '../../../src/wallet/accounts/interfaces/BaseAccountInterface'
import DAppAccount from '../../../src/wallet/accounts/models/DappAccount'
import VaultAccount from '../../../src/wallet/accounts/models/VaultAccount'
import DefaultAccount from '../../../src/wallet/accounts/models/DefaultAccount'

// Mock the WalletUtils
vi.mock('../../../src/utils/WalletUtils', () => ({
    AccountType: {
        NEXA_ACCOUNT: 0,
        VAULT_ACCOUNT: 1,
        DAPP_ACCOUNT: 2
    },
    getNextAccountIndex: vi.fn().mockResolvedValue(0),
    generateAccountKey: vi.fn().mockReturnValue({
        deriveChild: vi.fn().mockReturnValue({
            privateKey: {
                toAddress: vi.fn().mockReturnValue({
                    toString: vi.fn().mockReturnValue('nexatest:address123')
                })
            }
        })
    }),
    generateKeyAndAddress: vi.fn().mockReturnValue({
        key: {
            privateKey: {
                toAddress: vi.fn().mockReturnValue({
                    toString: vi.fn().mockReturnValue('nexatest:address123')
                })
            }
        },
        address: 'nexatest:address123',
        balance: '0',
        tokensBalance: {}
    }),
    generateKeysAndAddresses: vi.fn().mockReturnValue({
        receiveKeys: [{
            key: { privateKey: {} },
            address: 'nexatest:receive123',
            balance: '0',
            tokensBalance: {}
        }],
        changeKeys: [{
            key: { privateKey: {} },
            address: 'nexatest:change123',
            balance: '0',
            tokensBalance: {}
        }]
    })
}))

// Mock the account classes
vi.mock('../../../src/wallet/accounts/models/DappAccount', () => ({
    default: vi.fn().mockImplementation((purpose, index, addressKey) => ({
        purpose,
        index,
        addressKey,
        accountKeys: {
            receiveKeys: [addressKey],
            changeKeys: []
        },
        loadBalances: vi.fn().mockResolvedValue(undefined),
        getAccountStoreKey: vi.fn().mockReturnValue(`${purpose}.${index}`)
    }))
}))

vi.mock('../../../src/wallet/accounts/models/VaultAccount', () => ({
    default: vi.fn().mockImplementation((purpose, index, addressKey) => ({
        purpose,
        index,
        addressKey,
        accountKeys: {
            receiveKeys: [addressKey],
            changeKeys: []
        },
        loadBalances: vi.fn().mockResolvedValue(undefined),
        getAccountStoreKey: vi.fn().mockReturnValue(`${purpose}.${index}`)
    }))
}))

vi.mock('../../../src/wallet/accounts/models/DefaultAccount', () => ({
    default: vi.fn().mockImplementation((index, indexes, keys) => ({
        index,
        indexes,
        accountKeys: keys,
        loadBalances: vi.fn().mockResolvedValue(undefined),
        getAccountStoreKey: vi.fn().mockReturnValue(String(index))
    }))
}))

describe('AccountStore', () => {
    let accountStore: AccountStore
    let mockMasterKey: HDPrivateKey

    beforeEach(() => {
        accountStore = new AccountStore()
        mockMasterKey = {} as HDPrivateKey
        vi.clearAllMocks()
    })

    describe('constructor', () => {
        test('should create empty account store', () => {
            expect(accountStore).toBeInstanceOf(AccountStore)
            expect(accountStore.listAccounts().size).toBe(0)
        })
    })

    describe('createAccount', () => {
        test('should create DApp account', async () => {
            const account = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
            
            expect(account).toBeDefined()
            expect(DAppAccount).toHaveBeenCalledWith(2, 0, expect.any(Object))
            expect(accountStore.listAccounts().size).toBe(1)
        })

        test('should create Vault account', async () => {
            const account = await accountStore.createAccount(AccountType.VAULT_ACCOUNT, mockMasterKey)
            
            expect(account).toBeDefined()
            expect(VaultAccount).toHaveBeenCalledWith(1, 0, expect.any(Object))
            expect(accountStore.listAccounts().size).toBe(1)
        })

        test('should create Default NEXA account', async () => {
            const account = await accountStore.createAccount(AccountType.NEXA_ACCOUNT, mockMasterKey)
            
            expect(account).toBeDefined()
            expect(DefaultAccount).toHaveBeenCalledWith(0, expect.any(Object), expect.any(Object))
            expect(accountStore.listAccounts().size).toBe(1)
        })

        test('should return existing account if already created', async () => {
            const account1 = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
            const account2 = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
            
            expect(account1).toBe(account2)
            expect(accountStore.listAccounts().size).toBe(1)
        })
    })

    describe('getAccountStoreKey', () => {
        test('should generate correct key for DApp account', () => {
            const key = accountStore['getAccountStoreKey'](AccountType.DAPP_ACCOUNT, 0)
            expect(key).toBe('2.0')
        })

        test('should generate correct key for Vault account', () => {
            const key = accountStore['getAccountStoreKey'](AccountType.VAULT_ACCOUNT, 1)
            expect(key).toBe('1.1')
        })

        test('should generate correct key for Default account', () => {
            const key = accountStore['getAccountStoreKey'](AccountType.NEXA_ACCOUNT, 0)
            expect(key).toBe('0')
        })
    })

    describe('findKeyForAddress', () => {
        test('should find key for existing address', async () => {
            const mockAccount = {
                accountKeys: {
                    receiveKeys: [{
                        address: 'nexatest:target123',
                        key: { privateKey: 'mock-key' }
                    }],
                    changeKeys: [{
                        address: 'nexatest:change123',
                        key: { privateKey: 'mock-key-2' }
                    }]
                }
            }
            
            accountStore['_accounts'].set('test', mockAccount as any)
            
            const result = accountStore.findKeyForAddress('nexatest:target123')
            expect(result).toBeDefined()
            expect(result?.address).toBe('nexatest:target123')
        })

        test('should return null for non-existent address', () => {
            const result = accountStore.findKeyForAddress('nexatest:nonexistent')
            expect(result).toBeNull()
        })

        test('should search through both receive and change keys', async () => {
            const mockAccount = {
                accountKeys: {
                    receiveKeys: [{
                        address: 'nexatest:receive123',
                        key: { privateKey: 'mock-key' }
                    }],
                    changeKeys: [{
                        address: 'nexatest:change123',
                        key: { privateKey: 'mock-key-2' }
                    }]
                }
            }
            
            accountStore['_accounts'].set('test', mockAccount as any)
            
            const receiveResult = accountStore.findKeyForAddress('nexatest:receive123')
            const changeResult = accountStore.findKeyForAddress('nexatest:change123')
            
            expect(receiveResult?.address).toBe('nexatest:receive123')
            expect(changeResult?.address).toBe('nexatest:change123')
        })
    })

    describe('importAccount', () => {
        test('should import new account', () => {
            const mockAccount = {
                getAccountStoreKey: vi.fn().mockReturnValue('imported.0')
            } as any
            
            accountStore.importAccount(mockAccount)
            
            expect(accountStore.listAccounts().size).toBe(1)
            expect(accountStore.getAccount('imported.0')).toBe(mockAccount)
        })

        test('should throw error for duplicate account', () => {
            const mockAccount = {
                getAccountStoreKey: vi.fn().mockReturnValue('duplicate.0')
            } as any
            
            accountStore.importAccount(mockAccount)
            
            expect(() => {
                accountStore.importAccount(mockAccount)
            }).toThrow('Account already exists!')
        })
    })

    describe('exportAccount', () => {
        test('should export existing account', () => {
            const mockAccount = {
                getAccountStoreKey: vi.fn().mockReturnValue('export.0')
            } as any
            
            accountStore['_accounts'].set('export.0', mockAccount)
            
            const exported = accountStore.exportAccount('export.0')
            expect(exported).toBe(mockAccount)
        })

        test('should throw error for non-existent account', () => {
            expect(() => {
                accountStore.exportAccount('nonexistent')
            }).toThrow('Cannot find account!')
        })
    })

    describe('removeAccount', () => {
        test('should remove existing account', () => {
            const mockAccount = {
                getAccountStoreKey: vi.fn().mockReturnValue('remove.0')
            } as any
            
            accountStore['_accounts'].set('remove.0', mockAccount)
            expect(accountStore.listAccounts().size).toBe(1)
            
            accountStore.removeAccount('remove.0')
            expect(accountStore.listAccounts().size).toBe(0)
        })

        test('should throw error for non-existent account', () => {
            expect(() => {
                accountStore.removeAccount('nonexistent')
            }).toThrow('Cannot find account!')
        })
    })

    describe('listAccounts', () => {
        test('should return all accounts', async () => {
            await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
            await accountStore.createAccount(AccountType.VAULT_ACCOUNT, mockMasterKey)
            
            const accounts = accountStore.listAccounts()
            expect(accounts.size).toBe(2)
        })

        test('should return empty map when no accounts', () => {
            const accounts = accountStore.listAccounts()
            expect(accounts.size).toBe(0)
            expect(accounts).toBeInstanceOf(Map)
        })
    })

    describe('getAccount', () => {
        test('should return account by index', async () => {
            const account = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
            
            const retrieved = accountStore.getAccount('2.0')
            expect(retrieved).toBe(account)
        })

        test('should return undefined for non-existent account', () => {
            const retrieved = accountStore.getAccount('nonexistent')
            expect(retrieved).toBeUndefined()
        })
    })

    describe('error handling', () => {
        test('should handle account creation errors gracefully', async () => {
            // Mock getNextAccountIndex to throw an error
            const { getNextAccountIndex } = await import('../../../src/utils/WalletUtils')
            vi.mocked(getNextAccountIndex).mockRejectedValueOnce(new Error('Network error'))
            
            await expect(
                accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
            ).rejects.toThrow('Network error')
        })
    })

    describe('integration tests', () => {
        test('should handle multiple account types', async () => {
            const dappAccount = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
            const vaultAccount = await accountStore.createAccount(AccountType.VAULT_ACCOUNT, mockMasterKey)
            const nexaAccount = await accountStore.createAccount(AccountType.NEXA_ACCOUNT, mockMasterKey)
            
            expect(accountStore.listAccounts().size).toBe(3)
            expect(accountStore.getAccount('2.0')).toBe(dappAccount)
            expect(accountStore.getAccount('1.0')).toBe(vaultAccount)
            expect(accountStore.getAccount('0')).toBe(nexaAccount)
        })

        test('should maintain account independence', async () => {
            const account1 = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
            const account2 = await accountStore.createAccount(AccountType.VAULT_ACCOUNT, mockMasterKey)
            
            accountStore.removeAccount('2.0')
            
            expect(accountStore.getAccount('2.0')).toBeUndefined()
            expect(accountStore.getAccount('1.0')).toBe(account2)
        })
    })
})