import * as core from '@actions/core'
import artifact from '@actions/artifact'
import {run} from '../src/merge/merge-artifacts'
import {Inputs} from '../src/merge/constants'
import * as search from '../src/shared/search'

const fixtures = {
  artifactName: 'my-merged-artifact',
  tmpDirectory: '/tmp/merge-artifact',
  filesToUpload: [
    '/some/artifact/path/file-a.txt',
    '/some/artifact/path/file-b.txt',
    '/some/artifact/path/file-c.txt'
  ],
  artifacts: [
    {
      name: 'my-artifact-a',
      id: 1,
      size: 100,
      createdAt: new Date('2024-01-01T00:00:00Z')
    },
    {
      name: 'my-artifact-b',
      id: 2,
      size: 100,
      createdAt: new Date('2024-01-01T00:00:00Z')
    },
    {
      name: 'my-artifact-c',
      id: 3,
      size: 100,
      createdAt: new Date('2024-01-01T00:00:00Z')
    }
  ]
}

jest.mock('@actions/github', () => ({
  context: {
    repo: {
      owner: 'actions',
      repo: 'toolkit'
    },
    runId: 123,
    serverUrl: 'https://github.com'
  }
}))

jest.mock('@actions/core')

jest.mock('fs/promises', () => ({
  mkdtemp: jest.fn().mockResolvedValue('/tmp/merge-artifact'),
  rm: jest.fn().mockResolvedValue(undefined)
}))

/* eslint-disable no-unused-vars */
const mockInputs = (overrides?: Partial<{[K in Inputs]?: any}>) => {
  const inputs = {
    [Inputs.Name]: 'my-merged-artifact',
    [Inputs.Pattern]: '*',
    [Inputs.SeparateDirectories]: false,
    [Inputs.RetentionDays]: 0,
    [Inputs.CompressionLevel]: 6,
    [Inputs.DeleteMerged]: false,
    ...overrides
  }

  ;(core.getInput as jest.Mock).mockImplementation((name: string) => {
    return inputs[name]
  })
  ;(core.getBooleanInput as jest.Mock).mockImplementation((name: string) => {
    return inputs[name]
  })

  return inputs
}

describe('merge', () => {
  beforeEach(async () => {
    mockInputs()

    jest
      .spyOn(artifact, 'listArtifacts')
      .mockResolvedValue({artifacts: fixtures.artifacts})

    jest.spyOn(artifact, 'downloadArtifact').mockResolvedValue({
      downloadPath: fixtures.tmpDirectory
    })

    jest.spyOn(search, 'findFilesToUpload').mockResolvedValue({
      filesToUpload: fixtures.filesToUpload,
      rootDirectory: fixtures.tmpDirectory
    })

    jest.spyOn(artifact, 'uploadArtifact').mockResolvedValue({
      size: 123,
      id: 1337
    })

    jest
      .spyOn(artifact, 'deleteArtifact')
      .mockImplementation(async artifactName => {
        const artifact = fixtures.artifacts.find(a => a.name === artifactName)
        if (!artifact) throw new Error(`Artifact ${artifactName} not found`)
        return {id: artifact.id}
      })
  })

  it('merges artifacts', async () => {
    await run()

    for (const a of fixtures.artifacts) {
      expect(artifact.downloadArtifact).toHaveBeenCalledWith(a.id, {
        path: fixtures.tmpDirectory
      })
    }

    expect(artifact.uploadArtifact).toHaveBeenCalledWith(
      fixtures.artifactName,
      fixtures.filesToUpload,
      fixtures.tmpDirectory,
      {compressionLevel: 6}
    )
  })

  it('fails if no artifacts found', async () => {
    mockInputs({[Inputs.Pattern]: 'this-does-not-match'})

    expect(run()).rejects.toThrow()

    expect(artifact.uploadArtifact).not.toBeCalled()
    expect(artifact.downloadArtifact).not.toBeCalled()
  })

  it('supports custom compression level', async () => {
    mockInputs({
      [Inputs.CompressionLevel]: 2
    })

    await run()

    expect(artifact.uploadArtifact).toHaveBeenCalledWith(
      fixtures.artifactName,
      fixtures.filesToUpload,
      fixtures.tmpDirectory,
      {compressionLevel: 2}
    )
  })

  it('supports custom retention days', async () => {
    mockInputs({
      [Inputs.RetentionDays]: 7
    })

    await run()

    expect(artifact.uploadArtifact).toHaveBeenCalledWith(
      fixtures.artifactName,
      fixtures.filesToUpload,
      fixtures.tmpDirectory,
      {retentionDays: 7, compressionLevel: 6}
    )
  })

  it('supports deleting artifacts after merge', async () => {
    mockInputs({
      [Inputs.DeleteMerged]: true
    })

    await run()

    for (const a of fixtures.artifacts) {
      expect(artifact.deleteArtifact).toHaveBeenCalledWith(a.name)
    }
  })
})