import fs from "node:fs"
import path from "node:path"
import { VersionInfoSchema, type VersionInfoDto, type VersionReleaseSource } from "ttm-shared"

interface PackageJsonShape {
    name?: string
    version?: string
}

function normalizeString(value: unknown) {
    return typeof value === "string" && value.trim() ? value.trim() : null
}

function findProjectRoot(startDirectory: string) {
    let currentDirectory = path.resolve(startDirectory)

    while (true) {
        const packageJsonPath = path.join(currentDirectory, "package.json")
        if (fs.existsSync(packageJsonPath)) {
            try {
                const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as PackageJsonShape
                if (packageJson.name === "ttm_api") {
                    return currentDirectory
                }
            } catch {
                // ignore invalid package.json and keep walking upward
            }
        }

        const parentDirectory = path.dirname(currentDirectory)
        if (parentDirectory === currentDirectory) {
            return null
        }
        currentDirectory = parentDirectory
    }
}

function inferReleaseNameFromReleasePath(value: string | null) {
    if (!value) {
        return null
    }

    const normalizedValue = value.replace(/\\/gu, "/")
    const segments = normalizedValue.split("/").filter(Boolean)
    const releasesIndex = segments.lastIndexOf("releases")
    if (releasesIndex < 0 || releasesIndex + 1 >= segments.length) {
        return null
    }

    return normalizeString(segments[releasesIndex + 1])
}

function readLastLocalReleaseName(projectRoot: string | null) {
    if (!projectRoot) {
        return null
    }

    const lastReleasePath = path.resolve(projectRoot, "deploy", "runtime", "last-release.txt")
    if (!fs.existsSync(lastReleasePath)) {
        return null
    }

    try {
        return normalizeString(fs.readFileSync(lastReleasePath, "utf8"))
    } catch {
        return null
    }
}

function readPackageJson(projectRoot: string | null) {
    if (!projectRoot) {
        return { appName: null, appVersion: null }
    }

    const packageJsonPath = path.join(projectRoot, "package.json")
    if (!fs.existsSync(packageJsonPath)) {
        return { appName: null, appVersion: null }
    }

    try {
        const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as PackageJsonShape
        return {
            appName: normalizeString(packageJson.name),
            appVersion: normalizeString(packageJson.version),
        }
    } catch {
        return { appName: null, appVersion: null }
    }
}

function resolveCurrentReleaseName() {
    const envReleaseName = normalizeString(process.env.RELEASE_NAME)
    if (envReleaseName) {
        return {
            releaseName: envReleaseName,
            releaseSource: "env" as VersionReleaseSource,
        }
    }

    const cwd = process.cwd()
    const realpath = (() => {
        try {
            return fs.realpathSync(cwd)
        } catch {
            return null
        }
    })()
    const releaseNameFromRealpath = inferReleaseNameFromReleasePath(realpath)
    if (releaseNameFromRealpath) {
        return {
            releaseName: releaseNameFromRealpath,
            releaseSource: "cwd-realpath" as VersionReleaseSource,
            realpath,
        }
    }

    const projectRoot = findProjectRoot(cwd)
    const lastLocalReleaseName = readLastLocalReleaseName(projectRoot)
    if (lastLocalReleaseName) {
        return {
            releaseName: lastLocalReleaseName,
            releaseSource: "runtime-last-release" as VersionReleaseSource,
            realpath,
        }
    }

    return {
        releaseName: null,
        releaseSource: "none" as VersionReleaseSource,
        realpath,
    }
}

export const versionService = {
    getVersionInfo(): VersionInfoDto {
        const cwd = process.cwd()
        const projectRoot = findProjectRoot(cwd)
        const packageInfo = readPackageJson(projectRoot)
        const currentRelease = resolveCurrentReleaseName()

        return VersionInfoSchema.parse({
            appName: packageInfo.appName,
            appVersion: packageInfo.appVersion,
            releaseName: currentRelease.releaseName,
            releaseSource: currentRelease.releaseSource,
            nodeEnv: normalizeString(process.env.NODE_ENV),
            cwd,
            realpath: currentRelease.realpath ?? null,
        })
    },
}

