import { Cell, toNano } from '@ton/core'
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox'

import { UnitTestWrapper } from './UnitTestWrapper'
import { GlobalTestResults } from './types/GlobalTestResults'

import '@ton/test-utils'

export const throwIfFailedTest = (testName: string, testCases: { [key: string]: boolean }): string => {
    const passedTests = Object.keys(testCases).filter((test) => testCases[test])
    const failedTests = Object.keys(testCases).filter((test) => !testCases[test])

    let printStr = testName + '\n'
    if (passedTests.length > 0) {
        printStr += `\x1b[32m ✓ ${passedTests.length} tests passed.\n`
    }

    if (failedTests.length > 0) {
        printStr += `\x1b[31m ❌ ${failedTests.length} tests failed:\n`
        failedTests.forEach((test) => {
            printStr += `  - ${test}\n`
        })
        throw new Error(printStr)
    }
    return printStr
}

export const getTestSummary = (testName: string, testCases: { [key: string]: boolean }): string => {
    const passedTests = Object.keys(testCases).filter((test) => testCases[test])
    const failedTests = Object.keys(testCases).filter((test) => !testCases[test])

    let printStr = testName + '\n'
    if (passedTests.length > 0) {
        printStr += `\x1b[32m ✓ ${passedTests.length} tests passed.\n`
    }

    if (failedTests.length > 0) {
        printStr += `\x1b[31m ❌ ${failedTests.length} tests failed:\n`
        failedTests.forEach((test) => {
            printStr += `  - ${test}\n`
        })
    }
    return printStr
}

export const runUnitTests = (testName: string, code: Cell, globalTestResults: GlobalTestResults): GlobalTestResults => {
    describe(testName, () => {
        let blockchain: Blockchain
        let testCase: SandboxContract<UnitTestWrapper>
        let deployer: SandboxContract<TreasuryContract>

        beforeEach(async () => {
            blockchain = await Blockchain.create()

            testCase = blockchain.openContract(UnitTestWrapper.createFromConfig({}, code))

            deployer = await blockchain.treasury('deployer')

            const deployResult = await testCase.sendDeploy(deployer.getSender(), toNano('1'))

            expect(deployResult.transactions).toHaveTransaction({
                from: deployer.address,
                to: testCase.address,
                deploy: true,
                success: true,
            })
        })

        it(testName, async () => {
            let done = false
            const test_results: { [key: string]: boolean } = {}
            for (let testNum = 0n; !done; testNum++) {
                // the check is done inside beforeEach
                // blockchain and testCase are ready to use
                const result = await testCase.sendTest(deployer.getSender(), toNano('10000'), testNum)
                // search the json stringified result for the string "unhandled out-of-gas exception"
                const txns = result.transactions

                let unhandledException = false
                // starting from -1 because the first one is the external message from wallet to contract
                txns.forEach((item) => {
                    if (item.vmLogs.toString().includes('unhandled out-of-gas exception')) {
                        console.log(`Test number ${testNum} OUT OF GAS`)
                    }
                    if (item.vmLogs.toString().includes('default exception handler')) {
                        console.error(`Unhandled exception in ${testName} test ${testNum}`)
                        unhandledException = true
                    }
                })

                done = result.externals.some((item) => {
                    const event_topic = item.info.dest?.value
                    if (event_topic == 1n) {
                        return true
                    }
                    return false
                })
                if (!done) {
                    const contractTestName = await testCase.getTestName(deployer.getSender(), testNum)
                    let success = true
                    result.events.forEach((item) => {
                        // TODO fix this
                        // @ts-expect-error type mismatch
                        if (item.bounced as boolean) {
                            success = false
                        }
                    })
                    if (contractTestName in test_results) {
                        throw new Error(`Duplicate test name ${contractTestName}`)
                    }
                    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
                    test_results[contractTestName] = success && !unhandledException
                    globalTestResults[testName] = test_results
                    expect(success)
                }
            }
        })
    })

    return globalTestResults
}
