const chai = require('chai')
const fs = require('fs')
const mockfs = require('mock-fs')
const nock = require('nock')
const sinon = require('sinon')

chai.use(require('sinon-chai'))

const { expect } = chai

const {
  getVersions,
  checkNeedForBranchUpdate,
  updateBrowserVersionsFile,
  updatePRTitle,
  createPullRequest,
} = require('../../github-actions/update-browser-versions')

const coreStub = () => {
  return {
    setOutput: sinon.stub(),
  }
}

const stubOmahaResult = (result) => {
  nock('https://omahaproxy.appspot.com')
  .get('/all.json')
  .reply(200, result)
}

const stubRepoVersions = ({ betaVersion, stableVersion }) => {
  mockfs({
    './browser-versions.json': JSON.stringify({
      'chrome:beta': betaVersion,
      'chrome:stable': stableVersion,
    }),
  })
}

const stubOmahaVersions = ({ betaVersion, stableVersion }) => {
  stubOmahaResult([
    {
      os: 'linux',
      versions: [
        {
          channel: 'stable',
          version: stableVersion,
        },
        {
          channel: 'beta',
          version: betaVersion,
        },
      ],
    },
  ])
}

describe('update browser version github action', () => {
  beforeEach(() => {
    sinon.restore()
    mockfs.restore()
    nock.cleanAll()
  })

  context('.getVersions', () => {
    beforeEach(() => {
      stubRepoVersions({
        betaVersion: '1.1',
        stableVersion: '1.0',
      })
    })

    it('sets has_update: true when there is a stable update', async () => {
      stubOmahaVersions({
        betaVersion: '1.1',
        stableVersion: '2.0',
      })

      const core = coreStub()

      await getVersions({ core })

      expect(core.setOutput).to.be.calledWith('has_update', 'true')
    })

    it('sets has_update: true when there is a beta update', async () => {
      stubOmahaVersions({
        betaVersion: '1.2',
        stableVersion: '1.0',
      })

      const core = coreStub()

      await getVersions({ core })

      expect(core.setOutput).to.be.calledWith('has_update', 'true')
    })

    it('sets has_update: true when there is a stable update and a beta update', async () => {
      stubOmahaVersions({
        betaVersion: '2.1',
        stableVersion: '2.0',
      })

      const core = coreStub()

      await getVersions({ core })

      expect(core.setOutput).to.be.calledWith('has_update', 'true')
    })

    it('sets has_update: false when there is not a stable update or a beta update', async () => {
      stubOmahaVersions({
        betaVersion: '1.1',
        stableVersion: '1.0',
      })

      const core = coreStub()

      await getVersions({ core })

      expect(core.setOutput).to.be.calledWith('has_update', 'false')
    })

    it('sets has_update: false if there is a failure', async () => {
      stubOmahaResult({})

      const core = coreStub()

      await getVersions({ core })

      expect(core.setOutput).to.be.calledWith('has_update', 'false')
    })

    it('sets versions', async () => {
      stubOmahaVersions({
        betaVersion: '2.1',
        stableVersion: '2.0',
      })

      const core = coreStub()

      await getVersions({ core })

      expect(core.setOutput).to.be.calledWith('current_stable_version', '1.0')
      expect(core.setOutput).to.be.calledWith('latest_stable_version', '2.0')
      expect(core.setOutput).to.be.calledWith('current_beta_version', '1.1')
      expect(core.setOutput).to.be.calledWith('latest_beta_version', '2.1')
    })

    it('sets description correctly when there is a stable update', async () => {
      stubOmahaVersions({
        betaVersion: '1.1',
        stableVersion: '2.0',
      })

      const core = coreStub()

      await getVersions({ core })

      expect(core.setOutput).to.be.calledWith('description', 'Update Chrome (stable) to 2.0')
    })

    it('sets description correctly when there is a beta update', async () => {
      stubOmahaVersions({
        betaVersion: '1.2',
        stableVersion: '1.0',
      })

      const core = coreStub()

      await getVersions({ core })

      expect(core.setOutput).to.be.calledWith('description', 'Update Chrome (beta) to 1.2')
    })

    it('sets description correctly when there is a stable update and a beta update', async () => {
      stubOmahaVersions({
        betaVersion: '2.1',
        stableVersion: '2.0',
      })

      const core = coreStub()

      await getVersions({ core })

      expect(core.setOutput).to.be.calledWith('description', 'Update Chrome (stable) to 2.0 and Chrome (beta) to 2.1')
    })
  })

  context('.checkNeedForBranchUpdate', () => {
    beforeEach(() => {
      stubRepoVersions({
        betaVersion: '1.1',
        stableVersion: '1.0',
      })
    })

    it('sets has_newer_update: true when there is a stable update', () => {
      const core = coreStub()

      checkNeedForBranchUpdate({
        core,
        latestBetaVersion: '1.1',
        latestStableVersion: '2.0',
      })

      expect(core.setOutput).to.be.calledWith('has_newer_update', 'true')
    })

    it('sets has_newer_update: true when there is a beta update', () => {
      const core = coreStub()

      checkNeedForBranchUpdate({
        core,
        latestBetaVersion: '1.2',
        latestStableVersion: '1.0',
      })

      expect(core.setOutput).to.be.calledWith('has_newer_update', 'true')
    })

    it('sets has_newer_update: true when there is a stable update and a beta update', () => {
      const core = coreStub()

      checkNeedForBranchUpdate({
        core,
        latestBetaVersion: '2.1',
        latestStableVersion: '2.0',
      })

      expect(core.setOutput).to.be.calledWith('has_newer_update', 'true')
    })

    it('sets has_newer_update: false when there is not a stable update or a beta update', () => {
      const core = coreStub()

      checkNeedForBranchUpdate({
        core,
        latestBetaVersion: '1.1',
        latestStableVersion: '1.0',
      })

      expect(core.setOutput).to.be.calledWith('has_newer_update', 'false')
    })
  })

  context('.updateBrowserVersionsFile', () => {
    it('updates browser-versions.json with specified versions', () => {
      sinon.stub(fs, 'writeFileSync')

      updateBrowserVersionsFile({
        latestBetaVersion: '2.1',
        latestStableVersion: '2.0',
      })

      expect(fs.writeFileSync).to.be.calledWith('./browser-versions.json', `{
  "chrome:beta": "2.1",
  "chrome:stable": "2.0"
}
`)
    })
  })

  context('.updatePRTitle', () => {
    it('updates pull request title', async () => {
      const github = {
        pulls: {
          list: sinon.stub().returns(Promise.resolve(
            {
              data: [
                { number: '123' },
              ],
            },
          )),
          update: sinon.stub(),
        },
      }

      const context = {
        repo: {
          owner: 'cypress-io',
          repo: 'cypress',
        },
      }

      await updatePRTitle({
        context,
        github,
        baseBranch: 'develop',
        branchName: 'some-branch-name',
        description: 'Update Chrome to newer version',
      })

      expect(github.pulls.list).to.be.calledWith({
        owner: 'cypress-io',
        repo: 'cypress',
        base: 'develop',
        head: 'cypress-io:some-branch-name',
      })

      expect(github.pulls.update).to.be.calledWith({
        owner: 'cypress-io',
        repo: 'cypress',
        pull_number: '123',
        title: 'chore: Update Chrome to newer version',
      })
    })

    it('logs and does not attempt to update pull request title if PR cannot be found', async () => {
      const github = {
        pulls: {
          list: sinon.stub().returns(Promise.resolve(
            {
              data: [],
            },
          )),
          update: sinon.stub(),
        },
      }

      const context = {
        repo: {
          owner: 'cypress-io',
          repo: 'cypress',
        },
      }

      sinon.spy(console, 'log')

      await updatePRTitle({
        context,
        github,
        baseBranch: 'develop',
        branchName: 'some-branch-name',
        description: 'Update Chrome to newer version',
      })

      expect(github.pulls.list).to.be.calledWith({
        owner: 'cypress-io',
        repo: 'cypress',
        base: 'develop',
        head: 'cypress-io:some-branch-name',
      })

      expect(github.pulls.update).not.to.be.called
      // eslint-disable-next-line no-console
      expect(console.log).to.be.calledWith('Could not find PR for branch:', 'some-branch-name')
    })
  })

  context('.createPullRequest', () => {
    it('creates pull request with correct properties', async () => {
      const github = {
        pulls: {
          create: sinon.stub().returns(Promise.resolve()),
        },
      }

      const context = {
        repo: {
          owner: 'cypress-io',
          repo: 'cypress',
        },
      }

      await createPullRequest({
        context,
        github,
        baseBranch: 'develop',
        branchName: 'some-branch-name',
        description: 'Update Chrome',
      })

      expect(github.pulls.create).to.be.calledWith({
        owner: 'cypress-io',
        repo: 'cypress',
        base: 'develop',
        head: 'some-branch-name',
        title: 'chore: Update Chrome',
        body: 'This PR was auto-generated to update the version(s) of Chrome for driver tests',
        maintainer_can_modify: true,
      })
    })
  })
})
