import { describe, test, expect, beforeEach, vi } from 'vitest'
import { TransactionBuilder, Networks, PrivateKey } from 'libnexa-ts'
import WalletTransactionCreator from '../../../src/wallet/transactions/WalletTransactionCreator'
import { BaseAccount } from '../../../src/wallet/accounts/interfaces/BaseAccountInterface'
import { AddressKey } from '../../../src/models/wallet.entities'

// Mock the TXUtils functions
vi.mock('../../../src/utils/TXUtils', () => ({
    populateTokenAuth: vi.fn().mockResolvedValue([{} as PrivateKey]),
    buildCreateGroupTransaction: vi.fn().mockResolvedValue([{} as PrivateKey]),
    populateAndDuplicateTokenAuths: vi.fn().mockResolvedValue([{} as PrivateKey]),
    prepareDeleteTransaction: vi.fn().mockResolvedValue([{} as PrivateKey]),
    populateTokenInputsAndChange: vi.fn().mockResolvedValue([{} as PrivateKey]),
    populateNexaInputsAndChange: vi.fn().mockResolvedValue([{} as PrivateKey])
}))

// Mock the rostrumProvider
vi.mock('../../../src/network/RostrumProvider', () => ({
    rostrumProvider: {
        getUtxo: vi.fn().mockResolvedValue({
            tx_hash: 'mock-tx-hash',
            amount: 1000000,
            scriptpubkey: 'mock-scriptpubkey',
            addresses: ['nexatest:address123']
        }),
        getBlockTip: vi.fn().mockResolvedValue({
            height: 100000
        })
    }
}))

describe('WalletTransactionCreator', () => {
    let creator: WalletTransactionCreator
    let mockAccount: BaseAccount

    beforeEach(() => {
        mockAccount = {
            accountKeys: {
                receiveKeys: [
                    {
                        key: { privateKey: {} as PrivateKey },
                        address: 'nexatest:receive123',
                        balance: '1000000',
                        tokensBalance: {}
                    }
                ],
                changeKeys: [
                    {
                        key: { privateKey: {} as PrivateKey },
                        address: 'nexatest:change123',
                        balance: '500000',
                        tokensBalance: {}
                    }
                ]
            }
        } as BaseAccount

        creator = new WalletTransactionCreator(mockAccount)
    })

    describe('constructor', () => {
        test('should create instance with account', () => {
            expect(creator).toBeInstanceOf(WalletTransactionCreator)
        })

        test('should create instance with account and transaction builder', () => {
            const txBuilder = new TransactionBuilder()
            const creator = new WalletTransactionCreator(mockAccount, txBuilder)
            expect(creator).toBeInstanceOf(WalletTransactionCreator)
        })

        test('should throw error for account without receive keys', () => {
            const invalidAccount = {
                accountKeys: {
                    receiveKeys: [],
                    changeKeys: []
                }
            } as unknown as BaseAccount

            expect(() => {
                new WalletTransactionCreator(invalidAccount)
            }).toThrow('No receive keys available in account')
        })

        test('should throw error for account without account keys', () => {
            const invalidAccount = {} as BaseAccount

            expect(() => {
                new WalletTransactionCreator(invalidAccount)
            }).toThrow('Account keys are not initialized')
        })
    })

    describe('fromAccount', () => {
        test('should set account and return this for chaining', () => {
            const newAccount = {
                accountKeys: {
                    receiveKeys: [{
                        key: {privateKey: {} as PrivateKey},
                        address: 'nexatest:new123',
                        balance: '0',
                        tokensBalance: {}
                    }],
                    changeKeys: []
                }
            } as unknown as BaseAccount

            const result = creator.fromAccount(newAccount)

            expect(result).toBe(creator)
            expect(creator['_account']).toBe(newAccount)
        })
    })

    describe('parseTxHex', () => {
        test('should add parse operation to builder', () => {
            const hex = 'deadbeef'
            const result = creator.parseTxHex(hex)

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(1)
        })
    })

    describe('parseTxBuffer', () => {
        test('should add parse operation to builder', () => {
            const buffer = Buffer.from('deadbeef', 'hex')
            const result = creator.parseTxBuffer(buffer)

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(1)
        })
    })

    describe('mint', () => {
        test('should add mint operation to builder', () => {
            const token = 'nexatest:tqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
            const amount = '1000'

            const result = creator.mint(token, amount)

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(1)
        })

        test('should be chainable', () => {
            const token = 'nexatest:tqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
            const amount = '1000'

            const result = creator
                .mint(token, amount)
                .mint(token, amount)

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(2)
        })
    })

    describe('melt', () => {
        test('should add melt operation to builder', () => {
            const token = 'nexatest:tqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
            const amount = '1000'

            const result = creator.melt(token, amount)

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(1)
        })

        test('should be chainable', () => {
            const token = 'nexatest:tqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
            const amount = '1000'

            const result = creator
                .melt(token, amount)
                .melt(token, amount)

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(2)
        })
    })

    describe('populate', () => {
        test('should add populate operation to builder', () => {
            const result = creator.populate()

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(1)
        })

        test('should validate account before populating', () => {
            const invalidCreator = new WalletTransactionCreator(mockAccount)
            invalidCreator['_account'] = null as any

            expect(() => {
                invalidCreator.populate()
            }).toThrow('Account must be set before performing transactions')
        })
    })

    describe('sign', () => {
        test('should add sign operation to builder', () => {
            const result = creator.sign()

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(1)
        })

        test('should be chainable', () => {
            const result = creator
                .populate()
                .sign()

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(2)
        })
    })

    describe('findPrivateKeyFromAddress', () => {
        test('should find key for existing address', () => {
            const result = creator['findPrivateKeyFromAddress']('nexatest:receive123')

            expect(result).toBeDefined()
            expect(result?.address).toBe('nexatest:receive123')
        })

        test('should find key in change addresses', () => {
            const result = creator['findPrivateKeyFromAddress']('nexatest:change123')

            expect(result).toBeDefined()
            expect(result?.address).toBe('nexatest:change123')
        })

        test('should return undefined for non-existent address', () => {
            const result = creator['findPrivateKeyFromAddress']('nexatest:nonexistent')

            expect(result).toBeUndefined()
        })
    })

    describe('validateAccount', () => {
        test('should not throw for valid account', () => {
            expect(() => {
                creator['validateAccount']()
            }).not.toThrow()
        })

        test('should throw for null account', () => {
            creator['_account'] = null as any

            expect(() => {
                creator['validateAccount']()
            }).toThrow('Account must be set before performing transactions')
        })

        test('should throw for account without keys', () => {
            creator['_account'] = {} as BaseAccount

            expect(() => {
                creator['validateAccount']()
            }).toThrow('Account keys are not initialized')
        })

        test('should throw for account without receive keys', () => {
            creator['_account'] = {
                accountKeys: {
                    receiveKeys: [],
                    changeKeys: []
                }
            } as unknown as BaseAccount

            expect(() => {
                creator['validateAccount']()
            }).toThrow('No receive keys available in account')
        })
    })

    describe('integration tests', () => {
        test('should handle complete transaction flow', () => {
            const token = 'nexatest:tqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
            const amount = '1000'
            const toAddr = 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'

            const result = creator
                .onNetwork('testnet')
                .sendTo(toAddr, amount)
                .mint(token, amount)
                .populate()
                .sign()

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(4) // sendTo, mint, populate, sign
            expect(creator.network).toBe(Networks.testnet)
        })

        test('should handle token operations', () => {
            const token = 'nexatest:tqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
            const amount = '1000'

            const result = creator
                .mint(token, amount)
                .melt(token, amount)
                .populate()
                .sign()

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(4)
        })

        test('should handle token creation', () => {
            const result = creator
                .token('Test Token', 'TEST', 2, 'https://test.com', 'hash123')
                .populate()
                .sign()

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(3)
        })

        test('should handle transaction parsing and signing', () => {
            const hexTx = 'deadbeef'

            const result = creator
                .parseTxHex(hexTx)
                .sign()

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(2)
        })
    })

    describe('error handling', () => {
        test('should handle async errors in builder functions', async () => {
            const errorFunction = vi.fn().mockRejectedValue(new Error('Test error'))
            creator.builder = [errorFunction]

            await expect(creator.build()).rejects.toThrow('Test error')
        })

        test('should handle validation errors during operations', () => {
            const invalidCreator = new WalletTransactionCreator(mockAccount)
            invalidCreator['_account'] = null as any

            expect(() => {
                invalidCreator.mint('token', '1000')
            }).not.toThrow() // Error should be thrown when builder executes, not when adding to builder
        })
    })

    describe('network handling', () => {
        test('should set network correctly', () => {
            const result = creator.onNetwork('testnet')

            expect(result).toBe(creator)
            expect(creator.network).toBe(Networks.testnet)
        })

        test('should default to mainnet', () => {
            expect(creator.network).toBe(Networks.mainnet)
        })
    })
})
