import { diskUtil } from '@sprucelabs/spruce-skill-utils'
import { test, assert } from '@sprucelabs/test-utils'
import { errorAssert } from '@sprucelabs/test-utils'
import CommandServiceImpl from '../../../services/CommandService'
import LintService from '../../../services/LintService'
import AbstractCliTest from '../../../tests/AbstractCliTest'
import testUtil from '../../../tests/utilities/test.utility'

export default class DeployingASkillTest extends AbstractCliTest {
    private static readonly fastHerokuOptions = {
        teamName: process.env.HEROKU_TEAM_NAME ?? '',
        shouldBuildAndLint: false,
        shouldRunTests: false,
    }

    protected static async beforeEach() {
        await super.beforeEach()
        CommandServiceImpl.fakeCommand('which heroku', {
            code: 0,
        })
        CommandServiceImpl.fakeCommand('grep api.heroku.com ~/.netrc', {
            code: 0,
        })
        CommandServiceImpl.fakeCommand('git status', {
            code: 0,
        })
        CommandServiceImpl.fakeCommand('git init', {
            code: 0,
            stdout: 'Initialized empty Git repository in',
        })

        CommandServiceImpl.fakeCommand('git ls-remote heroku', {
            code: 0,
        })

        CommandServiceImpl.fakeCommand('which git', {
            code: 0,
        })

        CommandServiceImpl.fakeCommand('heroku create good-heroku-name', {
            code: 0,
        })

        CommandServiceImpl.fakeCommand('heroku create bad-heroku-name', {
            code: 1,
        })

        CommandServiceImpl.fakeCommand('heroku buildpacks:set heroku/nodejs', {
            code: 0,
        })

        CommandServiceImpl.fakeCommand(
            'git push --set-upstream heroku master',
            {
                code: 0,
            }
        )

        diskUtil.writeFile(this.resolvePath('Procfile'), 'web: npm run boot')
    }

    @test()
    protected static async hasDeployAction() {
        assert.isFunction(this.Action('deploy', 'heroku').execute)
    }

    @test()
    protected static async deployHaltedBecauseNotRegistered() {
        await this.FeatureFixture().installCachedFeatures('deploy')

        diskUtil.writeFile(this.resolvePath('src/index.ts'), 'aoeustahoesuntao')

        const results = await this.Action('deploy', 'heroku').execute({
            teamName: process.env.HEROKU_TEAM_NAME ?? '',
        })

        assert.isTruthy(results.errors)
        assert.isArray(results.errors)
        errorAssert.assertError(results.errors[0], 'DEPLOY_FAILED', {
            stage: 'skill',
        })
    }

    @test()
    protected static async deployHaltedWithBadBuild() {
        await this.FeatureFixture().installCachedFeatures('deploy')

        await this.getSkillFixture().registerCurrentSkill({
            name: 'haulted wth bad build',
        })

        diskUtil.writeFile(this.resolvePath('src/index.ts'), 'aoeustahoesuntao')

        const results = await this.Action('deploy', 'heroku').execute({
            teamName: process.env.HEROKU_TEAM_NAME ?? '',
        })

        assert.isTruthy(results.errors)
        assert.isArray(results.errors)
        errorAssert.assertError(results.errors[0], 'DEPLOY_FAILED', {
            stage: 'building',
        })
    }

    @test()
    protected static async healthCheckReportsNotDeployed() {
        const cli = await this.FeatureFixture().installCachedFeatures('deploy')
        const health = (await cli.checkHealth()) as any

        assert.isFalsy(health.errors)
        assert.isTruthy(health.deploy)
        assert.isEqual(health.deploy.status, 'passed')
        assert.isLength(health.deploy.deploys, 0)
    }

    @test()
    protected static async deployHaltedWithBadTest() {
        await this.FeatureFixture().installCachedFeatures('deployWithTests')

        LintService.enableLinting()

        await this.getSkillFixture().registerCurrentSkill({
            name: 'haulted wth bad test',
        })

        const promise = this.Action('test', 'create').execute({
            nameReadable: 'Test failed',
            nameCamel: 'testFailed',
            type: 'behavioral',
        })

        await this.waitForInput()
        await this.ui.sendInput('')

        await promise

        const results = await this.Action('deploy', 'heroku').execute({
            teamName: process.env.HEROKU_TEAM_NAME ?? '',
        })

        assert.isTruthy(results.errors)
        assert.isArray(results.errors)
        errorAssert.assertError(results.errors[0], 'TEST_FAILED')
    }

    @test()
    protected static async errorsIfHerokuClientNotInstalled() {
        CommandServiceImpl.fakeCommand('which heroku', {
            code: 1,
        })

        await this.FeatureFixture().installCachedFeatures('deploy')

        await this.getSkillFixture().registerCurrentSkill({
            name: 'heroku not installed',
        })

        const results = await this.Action('deploy', 'heroku').execute(
            this.fastHerokuOptions
        )

        assert.isTruthy(results.errors)

        errorAssert.assertError(results.errors[0], 'MISSING_DEPENDENCIES', {
            'dependencies[0].name': 'heroku',
        })
    }

    @test()
    protected static async errorsIfGitNotInstalled() {
        CommandServiceImpl.fakeCommand('which git', {
            code: 1,
        })

        await this.FeatureFixture().installCachedFeatures('deploy')

        await this.getSkillFixture().registerCurrentSkill({
            name: 'git not installed',
        })

        const results = await this.Action('deploy', 'heroku').execute(
            this.fastHerokuOptions
        )

        assert.isTruthy(results.errors)

        errorAssert.assertError(results.errors[0], 'MISSING_DEPENDENCIES', {
            'dependencies[0].name': 'git',
        })
    }

    @test()
    protected static async errorsIfNotInGitRepo() {
        CommandServiceImpl.fakeCommand('git status', {
            code: 128,
            stderr: 'fatal: not a git repository (or any of the parent directories): .git',
        })

        await this.FeatureFixture().installCachedFeatures('deploy')

        await this.getSkillFixture().registerCurrentSkill({
            name: 'not in git repo',
        })

        const promise = this.Action('deploy', 'heroku').execute(
            this.fastHerokuOptions
        )

        await this.waitForInput()

        await this.ui.sendInput('n')

        const results = await promise
        assert.isTruthy(results.errors)

        errorAssert.assertError(results.errors[0], 'DEPLOY_FAILED', {
            stage: 'git',
        })
    }

    @test()
    protected static async canCreateGitRepoIfNeeded() {
        CommandServiceImpl.fakeCommand('git status', {
            code: 128,
        })

        await this.FeatureFixture().installCachedFeatures('deploy')

        await this.getSkillFixture().registerCurrentSkill({
            name: 'creates git repo',
        })

        const promise = this.Action('deploy', 'heroku').execute(
            this.fastHerokuOptions
        )

        await this.waitForInput()

        CommandServiceImpl.fakeCommand('git status', {
            code: 0,
        })

        await this.ui.sendInput('y')

        const results = await promise

        assert.isFalsy(results.errors)
    }

    @test()
    protected static async errorsWhenNotLoggedIntoHerkou() {
        CommandServiceImpl.fakeCommand('grep api.heroku.com ~/.netrc', {
            code: 1,
        })

        await this.FeatureFixture().installCachedFeatures('deploy')

        await this.getSkillFixture().registerCurrentSkill({
            name: 'not logged into heroku',
        })

        const results = await this.Action('deploy', 'heroku').execute(
            this.fastHerokuOptions
        )

        assert.isTruthy(results.errors)

        errorAssert.assertError(results.errors[0], 'DEPLOY_FAILED', {
            stage: 'heroku',
        })
    }

    @test()
    protected static async failsWhenDeclineToCreateProcFile() {
        diskUtil.deleteFile(this.resolvePath('Procfile'))
        await this.FeatureFixture().installCachedFeatures('deploy')

        await this.getSkillFixture().registerCurrentSkill({
            name: 'decline proc file',
        })

        const promise = this.Action('deploy', 'heroku').execute(
            this.fastHerokuOptions
        )

        await this.waitForInput()

        assert.doesInclude(this.ui.invocations, {
            command: 'confirm',
        })
        await this.ui.sendInput('n')

        const results = await promise

        assert.isTruthy(results.errors)

        errorAssert.assertError(results.errors[0], 'DEPLOY_FAILED', {
            stage: 'procfile',
        })
    }

    @test()
    protected static async createsValidProcFile() {
        diskUtil.deleteFile(this.resolvePath('Procfile'))
        await this.FeatureFixture().installCachedFeatures('deploy')

        await this.getSkillFixture().registerCurrentSkill({
            name: 'valid proc file',
        })

        const promise = this.Action('deploy', 'heroku').execute(
            this.fastHerokuOptions
        )

        await this.waitForInput()

        await this.ui.sendInput('y')

        const results = await promise

        assert.isFalsy(results.errors)

        const match = testUtil.assertFileByNameInGeneratedFiles(
            'Procfile',
            results.files
        )

        const contents = diskUtil.readFile(match)

        assert.isEqual(contents, 'worker: npm run boot')
    }

    @test()
    protected static async failsWhenDeclineToCreateRemoteBranch() {
        CommandServiceImpl.fakeCommand('git ls-remote heroku', {
            code: 128,
        })

        await this.FeatureFixture().installCachedFeatures('deploy')

        await this.getSkillFixture().registerCurrentSkill({
            name: 'decline to create remote branch',
        })

        const promise = this.Action('deploy', 'heroku').execute(
            this.fastHerokuOptions
        )

        await this.waitForInput()

        assert.doesInclude(this.ui.invocations, {
            command: 'confirm',
        })
        await this.ui.sendInput('n')

        const results = await promise

        assert.isTruthy(results.errors)

        errorAssert.assertError(results.errors[0], 'DEPLOY_FAILED', {
            stage: 'remote',
        })
    }

    @test()
    protected static async asksForHerokuAppName() {
        CommandServiceImpl.fakeCommand('git ls-remote heroku', {
            code: 128,
        })

        await this.FeatureFixture().installCachedFeatures('deploy')

        await this.getSkillFixture().registerCurrentSkill({
            name: 'ask for app name',
        })

        const promise = this.Action('deploy', 'heroku').execute(
            this.fastHerokuOptions
        )

        await this.waitForInput()

        await this.ui.sendInput('y')

        await this.waitForInput()

        assert.doesInclude(this.ui.invocations, {
            command: 'prompt',
            options: {
                type: 'text',
            },
        })

        await this.ui.sendInput(`good-heroku-name`)

        const results = await promise

        assert.isFalsy(results.errors)
    }

    @test()
    protected static async keepsAskingForAppNameUntilAGoodOneIsSelected() {
        CommandServiceImpl.fakeCommand('git ls-remote heroku', {
            code: 128,
        })
        await this.FeatureFixture().installCachedFeatures('deploy')

        await this.getSkillFixture().registerCurrentSkill({
            name: 'haulted wth bad build',
        })

        const promise = this.Action('deploy', 'heroku').execute(
            this.fastHerokuOptions
        )

        await this.waitForInput()

        assert.doesInclude(this.ui.invocations, {
            command: 'confirm',
        })

        await this.ui.sendInput('y')

        await this.waitForInput()

        assert.doesInclude(this.ui.invocations, {
            command: 'prompt',
            options: {
                type: 'text',
            },
        })
        await this.ui.sendInput(`bad-heroku-name`)
        await this.waitForInput()
        await this.ui.sendInput('bad-heroku-name')
        await this.waitForInput()
        await this.ui.sendInput('good-heroku-name')

        const results = await promise

        assert.isFalsy(results.errors)
    }

    @test()
    protected static async failsWithPendingChangesToCommit() {
        CommandServiceImpl.fakeCommand('git status', {
            code: 0,
            stdout: 'Changes not staged for commit',
        })

        await this.FeatureFixture().installCachedFeatures('deploy')

        await this.getSkillFixture().registerCurrentSkill({
            name: 'pending changes to commit',
        })

        const results = await this.Action('deploy', 'heroku').execute(
            this.fastHerokuOptions
        )

        assert.isTruthy(results.errors)

        errorAssert.assertError(results.errors[0], 'DEPLOY_FAILED', {
            stage: 'git',
        })
    }

    @test()
    protected static async canDeploySkill() {
        await this.FeatureFixture().installCachedFeatures('deploy')

        await this.getSkillFixture().registerCurrentSkill({
            name: 'can deploy',
        })

        const results = await this.Action('deploy', 'heroku').execute(
            this.fastHerokuOptions
        )

        assert.isFalsy(results.errors)
    }
}
