import { describe, test, expect, beforeEach, vi } from 'vitest'
import { TransactionBuilder, Networks, Transaction } from 'libnexa-ts'
import { TransactionCreator } from '../../../src/wallet/transactions/interfaces/TransactionCreator'
import { PermissionLabel } from '../../../src/models/transaction.entities'

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

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

// Create a concrete implementation of TransactionCreator for testing
class TestTransactionCreator extends TransactionCreator {
    constructor(tx?: TransactionBuilder | string | Buffer) {
        super(tx)
    }

    public parseTxHex(tx: string): this {
        // Mock implementation
        return this
    }

    public parseTxBuffer(tx: Buffer): this {
        // Mock implementation
        return this
    }

    public populate(): this {
        // Mock implementation
        return this
    }
}

describe('TransactionCreator', () => {
    let creator: TestTransactionCreator

    beforeEach(() => {
        creator = new TestTransactionCreator()
    })

    describe('constructor', () => {
        test('should create instance without transaction builder', () => {
            const creator = new TestTransactionCreator()
            expect(creator).toBeInstanceOf(TransactionCreator)
            expect(creator.network).toBe(Networks.mainnet)
            expect(creator.totalValue).toBe(BigInt(0))
        })

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

    describe('onNetwork', () => {
        test('should set network by string', () => {
            const result = creator.onNetwork('testnet')
            expect(result).toBe(creator)
            expect(creator.network).toBe(Networks.testnet)
        })

        test('should set network by Networkish object', () => {
            const result = creator.onNetwork(Networks.testnet)
            expect(result).toBe(creator)
            expect(creator.network).toBe(Networks.testnet)
        })

        test('should be chainable', () => {
            const result = creator
                .onNetwork('testnet')
                .onNetwork('mainnet')
            expect(result).toBe(creator)
            expect(creator.network).toBe(Networks.mainnet)
        })
    })

    describe('property getters and setters', () => {
        test('should get and set txOptions', () => {
            const options = { feeFromAmount: true }
            creator.txOptions = options
            expect(creator.txOptions).toEqual(options)
        })

        test('should get and set network', () => {
            creator.network = Networks.testnet
            expect(creator.network).toBe(Networks.testnet)
        })

        test('should get and set totalValue', () => {
            const value = BigInt(1000)
            creator.totalValue = value
            expect(creator.totalValue).toBe(value)
        })

        test('should get and set builder', () => {
            const mockFn = vi.fn()
            creator.builder = [mockFn]
            expect(creator.builder).toHaveLength(1)
            expect(creator.builder[0]).toBe(mockFn)
        })

        test('should get and set tokens', () => {
            const tokenAction = {
                token: 'test-token',
                amount: BigInt(100),
                action: 'send'
            }
            creator.tokens.add(tokenAction)
            expect(creator.tokens.size).toBe(1)
            expect(creator.tokens.has(tokenAction)).toBe(true)
        })
    })

    describe('consolidate', () => {
        test('should add consolidate operation to builder', () => {
            const toAddr = 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
            const result = creator.consolidate(toAddr)

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

        test('should be chainable', () => {
            const toAddr = 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
            const result = creator
                .consolidate(toAddr)
                .consolidate(toAddr)

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

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

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

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

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

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

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

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

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

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

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

    describe('sendTo', () => {
        test('should add NEXA send operation to builder', () => {
            const toAddr = 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
            const amount = '1000'

            const result = creator.sendTo(toAddr, amount)

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

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

            const result = creator
                .sendTo(toAddr, amount)
                .sendTo(toAddr, amount)

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

    describe('renewAuthority', () => {
        test('should add renew authority operation to builder', () => {
            const token = 'nexatest:tq8r37lcjlqazz7vuvug84q2ev50573hesrnxkv9y6hvhhl5k5qqqnmyf79mx'
            const perms: PermissionLabel[] = ['mint', 'melt']

            const result = creator.renewAuthority(token, perms)

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

        test('should add renew authority operation with address', () => {
            const token = 'nexatest:tq8r37lcjlqazz7vuvug84q2ev50573hesrnxkv9y6hvhhl5k5qqqnmyf79mx'
            const perms: PermissionLabel[] = ['mint', 'melt']
            const toAddr = 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'

            const result = creator.renewAuthority(token, perms, toAddr)

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

    describe('deleteAuthority', () => {
        test('should add delete authority operation to builder', () => {
            const token = 'nexatest:tq8r37lcjlqazz7vuvug84q2ev50573hesrnxkv9y6hvhhl5k5qqqnmyf79mx'
            const outpoint = 'abc123:0'

            const result = creator.deleteAuthority(token, outpoint)

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

        test('should be chainable', () => {
            const token = 'nexatest:tq8r37lcjlqazz7vuvug84q2ev50573hesrnxkv9y6hvhhl5k5qqqnmyf79mx'
            const outpoint = 'abc123:0'

            const result = creator
                .deleteAuthority(token, outpoint)
                .deleteAuthority(token, outpoint)

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

    describe('legacy methods', () => {
        test('legacyToken should add operation to builder', () => {
            const result = creator.legacyToken('Test Token', 'TEST', 2, 'https://test.com', 'hash123')
            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(1)
        })

        test('legacyGroup should add operation to builder', () => {
            const result = creator.legacyGroup('Test Group', 'TGRP', 'https://group.com', 'hash456')
            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(1)
        })

        test('should be chainable', () => {
            const result = creator
                .legacyToken('Test Token', 'TEST', 2, 'https://test.com', 'hash123')
                .legacyGroup('Test Group', 'TGRP', 'https://group.com', 'hash456')
            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(2)
        })
    })

    describe('token', () => {
        test('should add token creation to builder', () => {
            const result = creator.token('Test Token', 'TEST', 2, 'https://test.com', 'hash123')

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

        test('should be chainable', () => {
            const result = creator
                .token('Test Token', 'TEST', 2, 'https://test.com', 'hash123')
                .token('Test Token 2', 'TEST2', 4, 'https://test2.com', 'hash456')

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

    describe('collection', () => {
        test('should add collection creation to builder', () => {
            const result = creator.collection('Test Collection', 'TCOL', 'https://collection.com', 'hash123')

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

        test('should be chainable', () => {
            const result = creator
                .collection('Test Collection', 'TCOL', 'https://collection.com', 'hash123')
                .collection('Test Collection 2', 'TCOL2', 'https://collection2.com', 'hash456')

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

    describe('NFT', () => {
        test('should add NFT creation to builder', () => {
            const parent = 'nexatest:tq8r37lcjlqazz7vuvug84q2ev50573hesrnxkv9y6hvhhl5k5qqqnmyf79mx'
            const result = creator.nft(parent, 'https://nft.com/content.zip', 'hash123')

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

        test('should be chainable', () => {
            const parent = 'nexatest:tq8r37lcjlqazz7vuvug84q2ev50573hesrnxkv9y6hvhhl5k5qqqnmyf79mx'
            const result = creator
                .nft(parent, 'https://nft1.com/content.zip', 'hash123')
                .nft(parent, 'https://nft2.com/content.zip', 'hash456')

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

    describe('addOpReturn', () => {
        test('should add OP_RETURN with string data', () => {
            const data = 'Hello World'
            const result = creator.addOpReturn(data)

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

        test('should add OP_RETURN with buffer data', () => {
            const data = Buffer.from('Hello World')
            const result = creator.addOpReturn(data)

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

        test('should add OP_RETURN with full script', () => {
            const data = 'Hello World'
            const result = creator.addOpReturn(data, true)

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

        test('should be chainable', () => {
            const result = creator
                .addOpReturn('Hello')
                .addOpReturn('World')

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

    describe('build', () => {
        test('should execute all builder functions and return serialized transaction', async () => {
            const mockFunction = vi.fn().mockResolvedValue(undefined)
            creator.builder = [mockFunction]

            const result = await creator.build()

            expect(mockFunction).toHaveBeenCalledTimes(1)
            expect(typeof result).toBe('string')
        })

        test('should execute multiple builder functions in order', async () => {
            const mockFunction1 = vi.fn().mockResolvedValue(undefined)
            const mockFunction2 = vi.fn().mockResolvedValue(undefined)
            creator.builder = [mockFunction1, mockFunction2]

            await creator.build()

            expect(mockFunction1).toHaveBeenCalledTimes(1)
            expect(mockFunction2).toHaveBeenCalledTimes(1)
        })
    })

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

            const result = creator
                .onNetwork('testnet')
                .sendTo(toAddr, amount)
                .sendToToken(toAddr, amount, token)
                .addOpReturn('Transaction data')
                .feeFromAmount()
                .populate()
            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(4) // sendTo, sendToToken, addOpReturn, feeFromAmount (populate not called yet)
            expect(creator.network).toBe(Networks.testnet)

            const tx = await creator.build()
            expect(typeof tx).toBe('string')
        })

        test('should handle token authority operations', async () => {
            const token = 'nexatest:tq8r37lcjlqazz7vuvug84q2ev50573hesrnxkv9y6hvhhl5k5qqqnmyf79mx'
            const perms: PermissionLabel[] = ['mint', 'melt']
            const outpoint = 'abc123:0'

            const result = creator
                .renewAuthority(token, perms)
                .deleteAuthority(token, outpoint)
                .populate()

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(2) // renewAuthority, deleteAuthority (populate not called yet)

            const tx = await creator.build()
            expect(typeof tx).toBe('string')
        })

        test('should handle token and collection creation', async () => {
            const parent = 'nexatest:tq8r37lcjlqazz7vuvug84q2ev50573hesrnxkv9y6hvhhl5k5qqqnmyf79mx'

            const result = creator
                .token('Test Token', 'TEST', 2, 'https://test.com', 'hash123')
                .collection('Test Collection', 'TCOL', 'https://collection.com', 'hash456')
                .nft(parent, 'https://nft.com/content.zip', 'hash789')
                .populate()

            expect(result).toBe(creator)
            expect(creator.builder).toHaveLength(3) // token, collection, NFT (populate not called yet)

            const tx = await creator.build()
            expect(typeof tx).toBe('string')
        })
    })

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