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

// Mock the WatchOnlyTXUtils functions
vi.mock('../../../src/utils/WatchOnlyTXUtils', () => ({
    watchOnlyPopulateTokenAuth: vi.fn().mockResolvedValue(['address1']),
    watchOnlyBuildCreateGroupTransaction: vi.fn().mockResolvedValue(['address2']),
    watchOnlyPopulateAndDuplicateTokenAuths: vi.fn().mockResolvedValue(['address3']),
    watchOnlyPrepareDeleteTransaction: vi.fn().mockResolvedValue(['address4']),
    watchOnlyPopulateTokenInputsAndChange: vi.fn().mockResolvedValue(['address5']),
    watchOnlyPopulateNexaInputsAndChange: vi.fn().mockResolvedValue(['address6'])
}))

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

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

    describe('constructor', () => {
        test('should create instance without transaction builder', () => {
            const creator = new WatchOnlyTransactionCreator()
            expect(creator).toBeInstanceOf(WatchOnlyTransactionCreator)
        })

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

    describe('parseTxBuffer', () => {
        test('should not throw error and return this for chaining', () => {
            const buffer = Buffer.from('test')
            const result = creator.parseTxBuffer(buffer)
            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(1)
        })
    })

    describe('parseTxHex', () => {
        test('should not throw error and return this for chaining', () => {
            const hex = 'deadbeef'
            const result = creator.parseTxHex(hex)
            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(1)
        })
    })

    describe('from', () => {
        test('should accept single address string', () => {
            const address = 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
            const result = creator.onNetwork('testnet').from(address)

            expect(result).toBe(creator) // Should return this for chaining
            expect(creator['_availableAddresses']).toHaveLength(1)
            expect(creator['_availableAddresses'][0].address).toBe(address)
        })

        test('should accept array of address strings', () => {
            const addresses = [
                'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc',
                'nexatest:nqtsq5g5dsgh6mwjchqypn8hvdrjue0xpmz293fl7rm926xv'
            ]
            const result = creator.onNetwork('testnet').from(addresses)

            expect(result).toBe(creator)
            expect(creator['_availableAddresses']).toHaveLength(2)
            expect(creator['_availableAddresses'][0].address).toBe(addresses[0])
            expect(creator['_availableAddresses'][1].address).toBe(addresses[1])
        })

        test('should accept array of WatchOnlyAddress objects', () => {
            const result = creator.from(mockAddresses)

            expect(result).toBe(creator)
            expect(creator['_availableAddresses']).toHaveLength(2)
            expect(creator['_availableAddresses'][0]).toEqual(mockAddresses[0])
            expect(creator['_availableAddresses'][1]).toEqual(mockAddresses[1])
        })

        test('should accept single WatchOnlyAddress object', () => {
            const address = mockAddresses[0]
            const result = creator.from(address)

            expect(result).toBe(creator)
            expect(creator['_availableAddresses']).toHaveLength(1)
            expect(creator['_availableAddresses'][0]).toEqual(address)
        })
    })

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

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

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

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

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

            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 toAddr = 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'

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

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

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

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

            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 be chainable', () => {
            const result = creator
                .from(mockAddresses)
                .populate()

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

    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')
                .from(mockAddresses)
                .mint(token, amount, toAddr)
                .melt(token, amount, toAddr)
                .populate()

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(3) // mint, melt, populate
            expect(creator.network).toBe(Networks.testnet)
        })

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

            const result = creator
                .from(mockAddresses)
                .sendTo(toAddr, amount)
                .sendToToken(toAddr, amount, token)
                .populate()

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(3) // sendTo, sendToToken, populate
        })

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

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(2) // token, populate
        })

        test('should handle consolidate transaction', () => {
            const toAddr = 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'

            const result = creator
                .from(mockAddresses)
                .consolidate(toAddr)
                .populate()

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(2) // consolidate, populate
        })
    })

    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)
        })
    })

    describe('error handling', () => {
        test('should handle empty addresses array', () => {
            const result = creator.from([])

            expect(result).toBe(creator)
            expect(creator['_availableAddresses']).toHaveLength(0)
        })

        test('should handle null/undefined addresses', () => {
            // Test with addresses that don't have the address property
            const invalidAddress = {} as WatchOnlyAddress
            const result = creator.from(invalidAddress)

            expect(result).toBe(creator)
            // Should not add invalid addresses
            expect(creator['_availableAddresses']).toHaveLength(0)
        })
    })
})
