import { describe, test, expect, beforeEach, vi } from 'vitest'
import { Networks, TransactionBuilder } from 'libnexa-ts'
import WatchOnlyWallet from '../../../src/wallet/WatchOnlyWallet'
import { WatchOnlyAddress } from '../../../src/models/wallet.entities'

// Mock the rostrumProvider
vi.mock('../../../src/network/RostrumProvider', () => ({
    rostrumProvider: {
        broadcast: vi.fn().mockResolvedValue('txid123'),
        subscribeToAddresses: vi.fn().mockResolvedValue(undefined)
    }
}))

// Mock the ValidationUtils
vi.mock('../../../src/utils/ValidationUtils', () => ({
    default: {
        validateArgument: vi.fn()
    }
}))

// Mock the WalletUtils
vi.mock('../../../src/utils/WalletUtils', () => ({
    isValidNexaAddress: vi.fn().mockReturnValue(true)
}))

describe('WatchOnlyWallet', () => {
    let mockAddresses: WatchOnlyAddress[]

    beforeEach(() => {
        mockAddresses = [
            { address: 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc' },
            { address: 'nexatest:nqtsq5g5dsgh6mwjchqypn8hvdrjue0xpmz293fl7rm926xv' }
        ]
    })

    describe('constructor', () => {
        test('should create wallet with valid addresses and default network', () => {
            const wallet = new WatchOnlyWallet(mockAddresses)
            
            expect(wallet).toBeInstanceOf(WatchOnlyWallet)
            expect(wallet.getWatchedAddresses()).toHaveLength(2)
            expect(wallet.getWatchedAddresses()[0].address).toBe(mockAddresses[0].address)
        })

        test('should create wallet with custom network', () => {
            const wallet = new WatchOnlyWallet(mockAddresses, 'testnet')
            
            expect(wallet).toBeInstanceOf(WatchOnlyWallet)
            expect(wallet['_network']).toBe(Networks.testnet)
        })

        test('should throw error for invalid network type', () => {
            expect(() => {
                new WatchOnlyWallet(mockAddresses, 123 as any)
            }).toThrow('Network must be a string')
        })

        test('should throw error for invalid network', () => {
            expect(() => {
                new WatchOnlyWallet(mockAddresses, 'invalidnetwork')
            }).toThrow('Invalid network: invalidnetwork')
        })

        test('should throw error for null addresses', () => {
            expect(() => {
                new WatchOnlyWallet(null as any)
            }).toThrow('addresesToWatch is required')
        })

        test('should throw error for non-array addresses', () => {
            expect(() => {
                new WatchOnlyWallet('not-an-array' as any)
            }).toThrow('addressesToWatch must be an array')
        })

        test('should throw error for empty addresses array', () => {
            expect(() => {
                new WatchOnlyWallet([])
            }).toThrow('addressesToWatch cannot be empty')
        })

        test('should throw error for invalid address object', () => {
            expect(() => {
                new WatchOnlyWallet(['invalid'] as any)
            }).toThrow('addressesToWatch[0] must be an object')
        })

        test('should throw error for missing address property', () => {
            expect(() => {
                new WatchOnlyWallet([{ notAddress: 'test' }] as any)
            }).toThrow('addressesToWatch[0].address must be a string')
        })

        test('should throw error for empty address string', () => {
            expect(() => {
                new WatchOnlyWallet([{ address: '' }])
            }).toThrow('addressesToWatch[0].address cannot be empty')
        })

        test('should throw error for duplicate addresses', () => {
            const duplicates = [
                { address: 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc' },
                { address: 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc' }
            ]
            expect(() => {
                new WatchOnlyWallet(duplicates)
            }).toThrow('Duplicate address found')
        })

        test('should handle optional xPub property', () => {
            const addressesWithXPub = [
                { 
                    address: 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc',
                    xPub: {} as any
                }
            ]
            const wallet = new WatchOnlyWallet(addressesWithXPub)
            
            expect(wallet.getWatchedAddresses()[0].xPub).toBeDefined()
        })

        test('should handle optional derivationPath property', () => {
            const addressesWithPath = [
                { 
                    address: 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc',
                    derivationPath: "m/44'/29223'/0'/0/0"
                }
            ]
            const wallet = new WatchOnlyWallet(addressesWithPath)
            
            expect(wallet.getWatchedAddresses()[0].derivationPath).toBe("m/44'/29223'/0'/0/0")
        })
    })

    describe('newTransaction', () => {
        let wallet: WatchOnlyWallet

        beforeEach(() => {
            wallet = new WatchOnlyWallet(mockAddresses, 'testnet')
        })

        test('should create transaction without parameters', () => {
            const tx = wallet.newTransaction()
            
            expect(tx).toBeDefined()
            expect(tx.network).toBe(Networks.testnet)
        })

        test('should create transaction with TransactionBuilder', () => {
            const txBuilder = new TransactionBuilder()
            const tx = wallet.newTransaction(txBuilder)
            
            expect(tx).toBeDefined()
        })

        test('should create transaction with hex string', () => {
            const hexTx = 'deadbeef'
            const tx = wallet.newTransaction(hexTx)
            
            expect(tx).toBeDefined()
        })

        test('should create transaction with buffer', () => {
            const bufferTx = Buffer.from('deadbeef', 'hex')
            const tx = wallet.newTransaction(bufferTx)
            
            expect(tx).toBeDefined()
        })
    })

    describe('sendTransaction', () => {
        let wallet: WatchOnlyWallet

        beforeEach(() => {
            wallet = new WatchOnlyWallet(mockAddresses)
        })

        test('should broadcast transaction and return txid', async () => {
            const txHex = 'deadbeef'
            const result = await wallet.sendTransaction(txHex)
            
            expect(result).toBe('txid123')
        })
    })

    describe('subscribeToAddressNotifications', () => {
        let wallet: WatchOnlyWallet

        beforeEach(() => {
            wallet = new WatchOnlyWallet(mockAddresses)
        })

        test('should subscribe to address notifications', async () => {
            const callback = vi.fn()
            await wallet.subscribeToAddressNotifications(callback)
            
            // Should not throw and complete successfully
            expect(callback).not.toHaveBeenCalled() // Callback is passed to provider, not called directly
        })
    })

    describe('getWatchedAddresses', () => {
        test('should return copy of watched addresses', () => {
            const wallet = new WatchOnlyWallet(mockAddresses)
            const addresses = wallet.getWatchedAddresses()
            
            expect(addresses).toHaveLength(2)
            expect(addresses).not.toBe(wallet['_addressesToWatch']) // Should be a copy
            expect(addresses[0].address).toBe(mockAddresses[0].address)
        })
    })

    describe('validation edge cases', () => {
        test('should trim whitespace from addresses', () => {
            const addressesWithWhitespace = [
                { address: '  nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc  ' }
            ]
            const wallet = new WatchOnlyWallet(addressesWithWhitespace)
            
            expect(wallet.getWatchedAddresses()[0].address).toBe('nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc')
        })

        test('should handle mixed address formats', () => {
            const mixedAddresses = [
                'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc',
                { address: 'nexatest:nqtsq5g5dsgh6mwjchqypn8hvdrjue0xpmz293fl7rm926xv' }
            ]
            // This should work with the updated from() method in transaction creator
            const wallet = new WatchOnlyWallet([
                { address: mixedAddresses[0] as string },
                mixedAddresses[1] as WatchOnlyAddress
            ])
            
            expect(wallet.getWatchedAddresses()).toHaveLength(2)
        })
    })

    describe('network handling', () => {
        test('should default to mainnet when network is undefined', () => {
            const wallet = new WatchOnlyWallet(mockAddresses, undefined)
            
            expect(wallet['_network']).toBe(Networks.mainnet)
        })

        test('should handle testnet correctly', () => {
            const wallet = new WatchOnlyWallet(mockAddresses, 'testnet')
            
            expect(wallet['_network']).toBe(Networks.testnet)
        })
    })
})
