import * as core from '@actions/core'
import * as artifact from '@actions/artifact'
import * as github from '@actions/github'
import * as glob from '@actions/glob'
import * as toolCache from '@actions/tool-cache'
import {Octokit} from '@octokit/rest'

import * as path from 'path'
import fs from 'fs'

import * as execution from './execution'
import * as layout from './repository-layout'

const DEPENDENCY_GRAPH_ARTIFACT = 'dependency-graph'
const DEPENDENCY_GRAPH_FILE = 'dependency-graph.json'

export async function generateDependencyGraph(executable: string | undefined): Promise<void> {
    const workspaceDirectory = layout.workspaceDirectory()
    const buildRootDirectory = layout.buildRootDirectory()
    const buildPath = getRelativePathFromWorkspace(buildRootDirectory)

    const initScript = path.resolve(
        __dirname,
        '..',
        '..',
        'src',
        'resources',
        'init-scripts',
        'github-dependency-graph.init.gradle'
    )
    const args = [
        `-Dorg.gradle.github.env.GRADLE_BUILD_PATH=${buildPath}`,
        '--init-script',
        initScript,
        ':GitHubDependencyGraphPlugin_generateDependencyGraph'
    ]

    await execution.executeGradleBuild(executable, buildRootDirectory, args)
    const dependencyGraphJson = copyDependencyGraphToBuildRoot(buildRootDirectory)

    const artifactClient = artifact.create()
    artifactClient.uploadArtifact(DEPENDENCY_GRAPH_ARTIFACT, [dependencyGraphJson], workspaceDirectory)
}

function copyDependencyGraphToBuildRoot(buildRootDirectory: string): string {
    const sourceFile = path.resolve(
        buildRootDirectory,
        'build',
        'reports',
        'github-dependency-graph-plugin',
        'github-dependency-snapshot.json'
    )

    const destFile = path.resolve(buildRootDirectory, DEPENDENCY_GRAPH_FILE)
    fs.copyFileSync(sourceFile, destFile)
    return destFile
}

export async function submitDependencyGraph(): Promise<void> {
    const workspaceDirectory = layout.workspaceDirectory()
    const octokit: Octokit = getOctokit()

    for (const jsonFile of await retrieveDependencyGraphs(octokit, workspaceDirectory)) {
        const jsonContent = fs.readFileSync(jsonFile, 'utf8')

        const jsonObject = JSON.parse(jsonContent)
        jsonObject.owner = github.context.repo.owner
        jsonObject.repo = github.context.repo.repo
        const response = await octokit.request('POST /repos/{owner}/{repo}/dependency-graph/snapshots', jsonObject)

        const relativeJsonFile = getRelativePathFromWorkspace(jsonFile)
        core.info(`Submitted ${relativeJsonFile}: ${JSON.stringify(response)}`)
        core.notice(`Submitted ${relativeJsonFile}: ${response.data.message}`)
    }
}

async function findDependencyGraphFiles(dir: string): Promise<string[]> {
    const globber = await glob.create(`${dir}/**/${DEPENDENCY_GRAPH_FILE}`)
    const graphFiles = globber.glob()
    core.info(`Found graph files in ${dir}: ${graphFiles}`)
    return graphFiles
}

async function retrieveDependencyGraphs(octokit: Octokit, workspaceDirectory: string): Promise<string[]> {
    if (github.context.payload.workflow_run) {
        return await retrieveDependencyGraphsForWorkflowRun(
            github.context.payload.workflow_run.id,
            octokit,
            workspaceDirectory
        )
    }
    return retrieveDependencyGraphsForCurrentWorkflow(workspaceDirectory)
}

async function retrieveDependencyGraphsForWorkflowRun(
    runId: number,
    octokit: Octokit,
    workspaceDirectory: string
): Promise<string[]> {
    // Find the workflow run artifacts named "dependency-graph"
    const artifacts = await octokit.rest.actions.listWorkflowRunArtifacts({
        owner: github.context.repo.owner,
        repo: github.context.repo.repo,
        run_id: runId
    })

    const matchArtifact = artifacts.data.artifacts.find(candidate => {
        return candidate.name === DEPENDENCY_GRAPH_ARTIFACT
    })

    if (matchArtifact === undefined) {
        throw new Error(`Dependency graph artifact not found. Has it been generated by workflow run '${runId}'?`)
    }

    // Download the dependency-graph artifact
    const download = await octokit.rest.actions.downloadArtifact({
        owner: github.context.repo.owner,
        repo: github.context.repo.repo,
        artifact_id: matchArtifact.id,
        archive_format: 'zip'
    })

    const downloadBuffer = download.data as ArrayBuffer
    const downloadZip = path.resolve(workspaceDirectory, 'dependency-graph.zip')
    fs.writeFileSync(downloadZip, Buffer.from(downloadBuffer))

    // Expance the dependency-graph zip and locate each dependency-graph JSON file
    const extractDir = path.resolve(workspaceDirectory, 'dependency-graph')
    const extracted = await toolCache.extractZip(downloadZip, extractDir)
    core.info(`Extracted dependency graph artifacts to ${extracted}: ${fs.readdirSync(extracted)}`)

    return findDependencyGraphFiles(extracted)
}

async function retrieveDependencyGraphsForCurrentWorkflow(workspaceDirectory: string): Promise<string[]> {
    const artifactClient = artifact.create()
    const downloadPath = path.resolve(workspaceDirectory, 'dependency-graph')
    await artifactClient.downloadArtifact(DEPENDENCY_GRAPH_ARTIFACT, downloadPath)
    return await findDependencyGraphFiles(downloadPath)
}

function getOctokit(): Octokit {
    return new Octokit({
        auth: getGithubToken()
    })
}

function getGithubToken(): string {
    return core.getInput('github-token', {required: true})
}

function getRelativePathFromWorkspace(file: string): string {
    const workspaceDirectory = layout.workspaceDirectory()
    return path.relative(workspaceDirectory, file)
}
