import { search } from "@inquirer/prompts";
import { execa } from "execa";
import Fuse from "fuse.js";
import { glob } from "glob";
import { task } from "hereby";
import path from "node:path";
import fs from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { parseArgs } from "node:util";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const { values } = parseArgs({
    args: process.argv.slice(3),
    options: {
        server: { type: "string", short: "s", multiple: true }, // 服务名称
        type: { type: "string", short: "t" }, // 生成类型：api, rpc, docker
        imageName: { type: "string", short: "i" }, // 镜像名字
    }
})

const Paths = {
    root: __dirname,
    desc: path.join(__dirname, "desc"),
    app: path.join(__dirname, "app"),
    getServiceName: (filePath) => path.basename(filePath, path.extname(filePath)),
    getOutputDir: (serviceName) => path.join(__dirname, "app", serviceName),
    pathlistToChoices: (filePaths) => filePaths.map(filePath => ({
        title: Paths.getServiceName(filePath),
        value: filePath,
    })),
    getDescFiles: async (pattern) => {
        return await glob(pattern, { cwd: Paths.desc, absolute: true });
    },
    getAllApi: async () => {
        const apiPattern = "api/*.api";
        return Paths.pathlistToChoices(await Paths.getDescFiles(apiPattern));
    },
    getAllProto: async () => {
        const protoPattern = "rpc/*.proto";
        return Paths.pathlistToChoices(await Paths.getDescFiles(protoPattern));
    },
    getAllservice: async () => {
        let all = [];
        const services = await fs.readdir(Paths.app);
        for (const service of services) {
            const servicePath = path.join(Paths.app, service);
            const svcTypes = await fs.readdir(servicePath);
            svcTypes.map(svcType => all.push({
                title: `${service} - ${svcType}`,
                value: path.join(servicePath, svcType, svcType !== "rpc" ? `${service}.go` : "pb.go"),
            }));
        }
        return all;
    },
}


const Generators = {
    async api(apiFile) {
        const serviceName = Paths.getServiceName(apiFile);
        const outputDir = path.join(Paths.getOutputDir(serviceName), 'api');

        await fs.mkdir(outputDir, { recursive: true });
        await run('goctl', [
            'api', 'go',
            '--api', apiFile,
            '--dir', outputDir,
            '--style', 'goZero'
        ]);
    },
    async rpc(protoFile) {
        const serviceName = Paths.getServiceName(protoFile);
        const outputDir = path.join(Paths.getOutputDir(serviceName), 'rpc');

        await fs.mkdir(outputDir, { recursive: true });
        await run('goctl', [
            'rpc', 'protoc', protoFile,
            `--proto_path=${path.join(Paths.desc, "rpc",)}`,
            `--go_out=${outputDir}`,
            `--go-grpc_out=${outputDir}`,
            `--zrpc_out=${outputDir}`,
            '--style=goZero',
        ]);
    },
    async docker(servicePath) {
        const dockerFiles = await glob("DockerFile", { cwd: __dirname, absolute: true });
        if (dockerFiles.length !== 0) {
            fs.rm(dockerFiles[0], { force: true });
        }
        await run('goctl', [
            "docker", "--go", path.relative(__dirname, servicePath)
        ])
    }
};


const GenerateConfig = {
    api: {
        getChoices: () => Paths.getAllApi(),
        prompt: "Select an API description file",
        generate: (path) => Generators.api(path),
    },
    rpc: {
        getChoices: () => Paths.getAllProto(),
        prompt: "Select a proto file",
        generate: (path) => Generators.rpc(path),
    },
    docker: {
        getChoices: () => Paths.getAllservice(),
        prompt: "Select a service to generate Dockerfile",
        generate: (path) => Generators.docker(path),
    }
};



async function run(cmd, args, opts = {}) {
    console.log(`>> ${cmd} ${args.join(' ')}`);
    return execa(cmd, args, {
        stdio: 'inherit',
        ...opts
    });
}


async function searchSelector(chooses, message) {
    const fuse = new Fuse(chooses, {
        keys: ['title'],
        threshold: 0.4,
    })
    return search({
        message,
        source: async (term) => {
            if (!term) {
                return chooses.map(s => ({ name: s.title, value: s.value }));
            }
            const result = fuse.search(term);
            return result.map(s => ({ name: s.item.title, value: s.item.value }));
        }
    })
}


async function generateHandle() {
    const type = values.type;
    if (!type || !GenerateConfig[type]) {
        console.error("Please specify valid -t <api|rpc|docker>");
        return;
    }
    const config = GenerateConfig[type];

    const input = values.server
        ? (Array.isArray(values.server) ? values.server[0] : values.server)
        : await searchSelector(await config.getChoices(), config.prompt);

    await config.generate(input);
}

async function buildImage(imageName) {
    await run("docker", ["build", "-t", imageName, "."])
}


export const init = task({
    name: "init",
    desc: "initialize the project",
    run: async () => {
        await run("go", ["install", "github.com/zeromicro/go-zero/tools/goctl@latest"]);
    }
})


export const tidy = task({
    name: "tidy",
    desc: "tidy go.mod and go.sum",
    run: async () => { run("go", ["mod", "tidy"]) },
})


export const build = task({
    name: "build",
    desc: "build docker image",
    run: async () => {
        if (values.imageName) {
            buildImage(values.imageName);
        } else {
            console.error("Please specify image name with -i <imageName>");
        }
    }
})


export const gen = task({
    name: "gen",
    desc: "generate API/RPC service code",
    run: generateHandle,
});


