// Copyright 2023-2024 Gentoo Authors
// Distributed under the terms of the GNU General Public License v2

open System
open System.IO
open System.Text.RegularExpressions
open System.Threading.Tasks

open SimpleLog.SimpleLog
open System.CommandLine
open System.CommandLine.Invocation

open Gdmt.Shared

let CommandName = "gdmt-genpwsh"
let CommandDescription = "maintenance tool to create a PowerShell distfile"

let CompressionTypeOption =
    new Option<string>([| "-C"; "--compression" |], "compression type to use")

CompressionTypeOption.SetDefaultValue "zst"

let PpwshVersionArgument =
    new Argument<string>("pwsh-version", "powershell version")

let SdkVersionOption =
    new Option<string>([| "-e"; "--sdk-ver" |], "compatible .NET SDK version")

SdkVersionOption.SetDefaultValue "8.0"

let SdkExecutableOption =
    new Option<string>([| "-x"; "--sdk-exe" |], "SDK executable path")

let TempBaseOption = new Option<string>([| "-t"; "--temp" |], "overwrite temp path")
TempBaseOption.SetDefaultValue(Path.GetTempPath())

let CommandHandler (context: InvocationContext) : Task =
    task {
        let options = context.ParseResult

        let compressionType = options.GetValueForOption CompressionTypeOption
        let pwshVersion = options.GetValueForArgument PpwshVersionArgument
        let sdkVersionOption = options.GetValueForOption SdkVersionOption
        let tempBase = options.GetValueForOption TempBaseOption

        let envMap =
            [ ("POWERSHELL_TELEMETRY_OPTOUT", "1")
              ("POWERSHELL_UPDATECHECK", "0")

              ("DOTNET_CLI_TELEMETRY_OPTOUT", "1")
              ("DOTNET_NOLOGO", "1")
              ("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", "1")
              ("MSBUILDDISABLENODEREUSE", "1")

              ("LogVerbosity", "minimal")
              ("UseSharedCompilation", "false") ]

        for (envKey, envVar) in envMap do
            Environment.SetEnvironmentVariable(envKey, envVar, EnvironmentVariableTarget.Process)

        let tempGuid = Guid.NewGuid().ToString()
        let tempName = $"gdmt_genpwsh_{tempGuid}"

        let tempPath = Path.Combine(tempBase, tempName)
        let tempPwshPath = Path.Combine(tempPath, $"pwsh-{pwshVersion}")

        let tempCache = Path.Combine(tempPath, "cache")

        let gdmtRestoreArgsBase =
            [ "--cache"; tempCache; "--sdk-ver"; sdkVersionOption ]
            @ match options.GetValueForOption SdkExecutableOption with
              | empty when String.IsNullOrEmpty empty -> []
              | s -> [ "--sdk-exe"; s ]

        let ExecGdmtRestore (proj: string) =
            let args = [ "gdmt-restore" ] @ gdmtRestoreArgsBase @ [ "--project"; proj ]

            ExecProcess(args).Run().Check()

        // Info

        LogMessage Debug $"Temp path:       {tempPath}"
        LogMessage Debug $"Temp PWSH path:  {tempPwshPath}"

        // Clone PowerShell source repository.

        Directory.CreateDirectory tempPath |> ignore
        Environment.CurrentDirectory <- tempPath

        ExecProcess(
            [ "git"
              "clone"
              "--branch"
              $"v{pwshVersion}"
              "--depth"
              "1"
              "--recursive"
              "--shallow-submodules"
              "https://github.com/PowerShell/PowerShell"
              tempPwshPath ]
        )
            .Run()
            .Check()

        Environment.CurrentDirectory <- tempPwshPath

        // Remove "global.json".

        ExecProcess([ "rm"; "global.json" ]).Run() |> ignore

        // Restore needed subprojects.

        let src = Path.Combine(tempPwshPath, "src")

        let resGenDir = Path.Combine(src, "ResGen")
        let typeCatalogGenDir = Path.Combine(src, "TypeCatalogGen")
        let typeCatalogGenInc = Path.Combine(typeCatalogGenDir, "powershell.inc")

        let corePsTypeCatalogFile =
            Path.Combine(src, "System.Management.Automation/CoreCLR/CorePsTypeCatalog.cs")

        LogMessage Debug "Restoring PowerShell subproject: powershell-unix"
        ExecGdmtRestore "src/powershell-unix"

        LogMessage Debug "Restoring PowerShell subproject: ResGen"
        ExecGdmtRestore resGenDir

        LogMessage Debug "Restoring PowerShell subproject: TypeCatalogGen"
        ExecGdmtRestore typeCatalogGenDir

        // Microsoft.PowerShell.SDK.csproj

        LogMessage Debug "Updating Microsoft.PowerShell.SDK"

        let msPwshSdkPath = Path.Combine(src, "Microsoft.PowerShell.SDK")
        let msPwshSdkObjPath = Path.Combine(msPwshSdkPath, "obj")

        let msPwshSdkObjTargetsPath =
            Path.Combine(msPwshSdkObjPath, "Microsoft.PowerShell.SDK.csproj.TypeCatalog.targets")

        let msPwshSdkObjTargetsContent =
            """<Project>
  <Target
      Name="_GetDependencies"
      DependsOnTargets="ResolveAssemblyReferencesDesignTime">
    <ItemGroup>
      <_RefAssemblyPath
          Include="%(_ReferencesFromRAR.HintPath)%3B"
          Condition=" '%(_ReferencesFromRAR.NuGetPackageId)' != 'Microsoft.Management.Infrastructure' " />
    </ItemGroup>
    <WriteLinesToFile
          File="$(_DependencyFile)"
          Lines="@(_RefAssemblyPath)" Overwrite="true" />
  </Target>
</Project>
"""

        Directory.CreateDirectory msPwshSdkObjPath |> ignore

        use msPwshSdkObjTargetsWriter = new StreamWriter(msPwshSdkObjTargetsPath)

        do msPwshSdkObjTargetsWriter.Write(msPwshSdkObjTargetsContent)
        msPwshSdkObjTargetsWriter.Flush()

        ExecProcess(
            [ "dotnet"
              "msbuild"
              Path.Combine(msPwshSdkPath, "Microsoft.PowerShell.SDK.csproj")
              "/t:_GetDependencies"
              $"'/property:DesignTimeBuild=true;_DependencyFile={typeCatalogGenInc}'" ]
        )
            .Run()
            .Check()

        if not (File.Exists typeCatalogGenInc) then
            LogMessage Error $"Missing file: {typeCatalogGenInc}"

            $"Required file {typeCatalogGenInc} was not generated, can not proceed"
            |> Exception
            |> raise

        // Create powershell.version

        LogMessage Debug "Creating powershell.version"

        use powerShellVersionWriter =
            new StreamWriter(Path.Combine(tempPwshPath, "powershell.version"))

        do powerShellVersionWriter.Write($"v{pwshVersion}")
        powerShellVersionWriter.Flush()

        // ResGen

        LogMessage Debug "Running PowerShell subproject: ResGen"
        Environment.CurrentDirectory <- resGenDir

        ExecProcess([ "dotnet"; "run" ]).Run().Check()

        LogMessage Success "PowerShell subproject run succeed"

        // TypeCatalogGen

        LogMessage Debug "Running PowerShell subproject: TypeCatalogGen"
        Environment.CurrentDirectory <- typeCatalogGenDir

        ExecProcess([ "dotnet"; "run"; corePsTypeCatalogFile; typeCatalogGenInc ])
            .Run()
            .Check()

        LogMessage Success "PowerShell subproject run succeed"

        // Restore all again

        LogMessage Debug "Performing full restore of PowerShell"
        Environment.CurrentDirectory <- tempPwshPath

        ExecGdmtRestore "src/powershell-unix/powershell-unix.csproj"
        ExecGdmtRestore "src/Modules/PSGalleryModules.csproj"
        ExecGdmtRestore "PowerShell.sln"

        LogMessage Success "Full restore succeed"

        // Set fake git version for Portage builds

        let powerShellCommonPropsFile =
            Path.Combine(tempPwshPath, "PowerShell.Common.props")

        let powerShellCommonPropsContents = File.ReadAllText(powerShellCommonPropsFile)

        let powerShellCommonPropsNewContents =
            Regex("git describe --abbrev=60 --long")
                .Replace(powerShellCommonPropsContents, $"echo v{pwshVersion}-0-g0")

        use powerShellCommonPropsWriter = new StreamWriter(powerShellCommonPropsFile)

        do powerShellCommonPropsWriter.Write(powerShellCommonPropsNewContents)
        powerShellCommonPropsWriter.Flush()

        // Cleanup

        LogMessage Debug "Cleaning up"

        for objDir in [ ".cache"; ".git"; "bin"; "obj" ] do
            let args =
                [ "find"
                  tempPwshPath
                  "-type"
                  "d"
                  "-name"
                  objDir
                  "-exec"
                  "rm"
                  "-f"
                  "-r"
                  "{}"
                  "+" ]

            ExecProcess(args).Run() |> ignore

        LogMessage Success "Cleaned up"

        // Tar up

        let pwshDistTarballPath =
            Path.Combine(tempPath, $"pwsh-{pwshVersion}.tar.{compressionType}")

        LogMessage Debug "Creating a tarball"
        Environment.CurrentDirectory <- tempPath

        Archive.CreateArchive $"pwsh-{pwshVersion}" pwshDistTarballPath
        LogMessage Success $"Created {pwshDistTarballPath}"

        ()
    }

[<EntryPoint>]
let main argv =
    let rootCommand = RootCommand(CommandName)

    rootCommand.Name <- CommandName
    rootCommand.Description <- CommandDescription

    rootCommand.AddArgument PpwshVersionArgument

    rootCommand.AddOption CompressionTypeOption
    rootCommand.AddOption SdkVersionOption
    rootCommand.AddOption SdkExecutableOption
    rootCommand.AddOption TempBaseOption

    rootCommand.SetHandler CommandHandler

    rootCommand.Invoke(argv)
