import "dotenv/config";
import * as childProcess from "child_process";
import semver from "semver";
import { getPrompt } from "../generate-changelog.mjs";
import fs from "fs";
import tar from "tar";

export const dryRun = process.env.RELEASE_DRY_RUN === "1";

if (dryRun) {
    console.info("Dry run enabled.");
}

/**
 * Check if docker is running
 * @returns {void}
 */
export function checkDocker() {
    try {
        childProcess.execSync("docker ps");
    } catch (error) {
        console.error("Docker is not running. Please start docker and try again.");
        process.exit(1);
    }
}

/**
 * Get Docker Hub repository name
 * @returns {string[]} List of repository names
 */
export function getRepoNames() {
    if (process.env.RELEASE_REPO_NAMES) {
        // Split by comma
        return process.env.RELEASE_REPO_NAMES.split(",").map((name) => name.trim());
    }
    return ["louislam/uptime-kuma", "ghcr.io/louislam/uptime-kuma"];
}

/**
 * Build frontend dist
 * @returns {void}
 */
export function buildDist() {
    if (!dryRun) {
        childProcess.execSync("npm run build", { stdio: "inherit" });
    } else {
        console.info("[DRY RUN] npm run build");
    }
}

/**
 * Build docker image and push to Docker Hub
 * @param {string[]} repoNames Docker Hub repository names
 * @param {string[]} tags Docker image tags
 * @param {string} target Dockerfile's target name
 * @param {string} buildArgs Docker build args
 * @param {string} dockerfile Path to Dockerfile
 * @param {string} platform Build platform
 * @returns {void}
 */
export function buildImage(
    repoNames,
    tags,
    target,
    buildArgs = "",
    dockerfile = "docker/dockerfile",
    platform = "linux/amd64,linux/arm64,linux/arm/v7"
) {
    let args = ["buildx", "build", "-f", dockerfile, "--platform", platform];

    for (let repoName of repoNames) {
        // Add tags
        for (let tag of tags) {
            args.push("-t", `${repoName}:${tag}`);
        }
    }

    args = [...args, "--target", target];

    // Add build args
    if (buildArgs) {
        args.push("--build-arg", buildArgs);
    }

    args = [...args, ".", "--push"];

    if (!dryRun) {
        childProcess.spawnSync("docker", args, { stdio: "inherit" });
    } else {
        console.log(`[DRY RUN] docker ${args.join(" ")}`);
    }
}

/**
 * Check if the version already exists on Docker Hub
 * TODO: use semver to compare versions if it is greater than the previous?
 * @param {string[]} repoNames repository name (Only check the name with single slash)
 * @param {string} version Version to check
 * @returns {void}
 */
export async function checkTagExists(repoNames, version) {
    // Skip if the tag is not on Docker Hub
    // louislam/uptime-kuma
    let dockerHubRepoNames = repoNames.filter((name) => {
        return name.split("/").length === 2;
    });

    for (let repoName of dockerHubRepoNames) {
        await checkTagExistsSingle(repoName, version);
    }
}

/**
 * Check if the version already exists on Docker Hub
 * @param {string} repoName repository name
 * @param {string} version Version to check
 * @returns {Promise<void>}
 */
export async function checkTagExistsSingle(repoName, version) {
    console.log(`Checking if version ${version} exists on Docker Hub:`, repoName);

    // Get a list of tags from the Docker Hub repository
    let tags = [];

    // It is mainly to check my careless mistake that I forgot to update the release version in .env, so `page_size` is set to 100 is enough, I think.
    const response = await fetch(`https://hub.docker.com/v2/repositories/${repoName}/tags/?page_size=100`);
    if (response.ok) {
        const data = await response.json();
        tags = data.results.map((tag) => tag.name);
    } else {
        console.error("Failed to get tags from Docker Hub");
        process.exit(1);
    }

    // Check if the version already exists
    if (tags.includes(version)) {
        console.error(`Version ${version} already exists`);
        process.exit(1);
    }
}

/**
 * Check the version format
 * @param {string} version Version to check
 * @returns {void}
 */
export function checkVersionFormat(version) {
    if (!version) {
        console.error("VERSION is required");
        process.exit(1);
    }

    // Check the version format, it should be a semver and must be like this: "2.0.0-beta.0"
    if (!semver.valid(version)) {
        console.error("VERSION is not a valid semver version");
        process.exit(1);
    }
}

/**
 * Press any key to continue
 * @returns {Promise<void>}
 */
export function pressAnyKey() {
    console.log("Git Push and Publish the release note on github, then press any key to continue");
    process.stdin.setRawMode(true);
    process.stdin.resume();
    return new Promise((resolve) =>
        process.stdin.once("data", (data) => {
            process.stdin.setRawMode(false);
            process.stdin.pause();
            resolve();
        })
    );
}

/**
 * Append version identifier
 * @param {string} version Version
 * @param {string} identifier Identifier
 * @returns {string} Version with identifier
 */
export function ver(version, identifier) {
    const obj = semver.parse(version);

    if (obj.prerelease.length === 0) {
        obj.prerelease = [identifier];
    } else {
        obj.prerelease[0] = [obj.prerelease[0], identifier].join("-");
    }
    return obj.format();
}

/**
 * Upload artifacts to GitHub
 * docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain
 * @param {string} version Version
 * @param {string} githubToken GitHub token
 * @returns {void}
 * @deprecated
 */
export function uploadArtifacts(version, githubToken) {
    let args = [
        "buildx",
        "build",
        "-f",
        "docker/dockerfile",
        "--platform",
        "linux/amd64",
        "-t",
        "louislam/uptime-kuma:upload-artifact",
        "--build-arg",
        `VERSION=${version}`,
        "--build-arg",
        "GITHUB_TOKEN",
        "--target",
        "upload-artifact",
        ".",
        "--progress",
        "plain",
    ];

    if (!dryRun) {
        childProcess.spawnSync("docker", args, {
            stdio: "inherit",
            env: {
                ...process.env,
                GITHUB_TOKEN: githubToken,
            },
        });
    } else {
        console.log(`[DRY RUN] docker ${args.join(" ")}`);
    }
}

/**
 * Execute a command
 * @param {string} cmd Command to execute
 * @returns {void}
 */
export function execSync(cmd) {
    if (!dryRun) {
        childProcess.execSync(cmd, { stdio: "inherit" });
    } else {
        console.info(`[DRY RUN] ${cmd}`);
    }
}

/**
 * Check if the current branch matches the expected release branch pattern
 * @param {string} expectedBranch Expected branch name (can be "release" or "release-{version}")
 * @returns {void}
 */
export function checkReleaseBranch(expectedBranch = "release") {
    const res = childProcess.spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
    const branch = res.stdout.toString().trim();
    if (branch !== expectedBranch) {
        console.error(`Current branch is ${branch}, please switch to "${expectedBranch}" branch`);
        process.exit(1);
    }
}

/**
 * Create dist.tar.gz from the dist directory
 * Similar to "tar -zcvf dist.tar.gz dist", but using nodejs
 * @returns {Promise<void>}
 */
export async function createDistTarGz() {
    const distPath = "dist";
    const outputPath = "./tmp/dist.tar.gz";
    const tmpDir = "./tmp";

    // Ensure tmp directory exists
    if (!fs.existsSync(tmpDir)) {
        fs.mkdirSync(tmpDir, { recursive: true });
    }

    // Check if dist directory exists
    if (!fs.existsSync(distPath)) {
        console.error("Error: dist directory not found");
        process.exit(1);
    }

    console.log(`Creating ${outputPath} from ${distPath}...`);

    try {
        await tar.create(
            {
                gzip: true,
                file: outputPath,
            },
            [distPath]
        );
        console.log(`Successfully created ${outputPath}`);
    } catch (error) {
        console.error(`Failed to create tarball: ${error.message}`);
        process.exit(1);
    }
}

/**
 * Create a draft release PR
 * @param {string} version Version
 * @param {string} previousVersion Previous version tag
 * @param {boolean} dryRun Still create the PR, but add "[DRY RUN]" to the title
 * @param {string} branchName The branch name to use for the PR head (defaults to "release")
 * @param {string} githubRunId The GitHub Actions run ID for linking to artifacts
 * @returns {Promise<void>}
 */
export async function createReleasePR(version, previousVersion, dryRun, branchName = "release", githubRunId = null) {
    const prompt = await getPrompt(previousVersion);

    const title = dryRun ? `chore: update to ${version} (dry run)` : `chore: update to ${version}`;

    // Build the artifact link - use direct run link if available, otherwise link to workflow file
    const artifactLink = githubRunId
        ? `https://github.com/louislam/uptime-kuma/actions/runs/${githubRunId}/workflow`
        : `https://github.com/louislam/uptime-kuma/actions/workflows/beta-release.yml`;

    const body = `## Release ${version}

This PR prepares the release for version ${version}.

### Manual Steps Required
- [ ] Merge this PR (squash and merge)
- [ ] Create a new release on GitHub with the tag \`${version}\`.
- [ ] Ask any LLM to categorize the changelog into sections.
- [ ] Place the changelog in the release note.
- [ ] Download the \`dist.tar.gz\` artifact from the [workflow run](${artifactLink}) and upload it to the release.
- [ ] (Beta only) Set prerelease
- [ ] Publish the release note on GitHub.

### Ask LLM to categorize the changelog

\`\`\`md
${prompt}
\`\`\`

Run the following command to generate the changelog with the categorized map from LLM:

\`\`\`bash
npm run generate-changelog ${previousVersion} generate 'JSON_MAPPING_BY_LLM_HERE'
\`\`\`

### Release Artifacts
The \`dist.tar.gz\` archive will be available as an artifact in the workflow run.
`;

    // Create the PR using gh CLI
    const args = [
        "pr",
        "create",
        "--title",
        title,
        "--body",
        body,
        "--base",
        "master",
        "--head",
        branchName,
        "--draft",
    ];

    console.log(`Creating draft PR: ${title}`);

    const result = childProcess.spawnSync("gh", args, {
        encoding: "utf-8",
        stdio: "inherit",
        env: {
            ...process.env,
            GH_TOKEN: process.env.GH_TOKEN || process.env.GITHUB_TOKEN,
        },
    });

    if (result.status !== 0) {
        console.error("Failed to create pull request");
        process.exit(1);
    }

    console.log("Successfully created draft pull request");
}
