#!/bin/pwsh
using namespace System.Security.Principal;

. "$PSScriptRoot/../Scripts/Prerequisites.ps1";

. "$PSScriptRoot/Manage.ps1";
. "$PSScriptRoot/User/Install.ps1";
. "$PSScriptRoot/../Scripts/Security.ps1";
. "$PSScriptRoot/../Scripts/WSL.ps1";
. "$PSScriptRoot/../Software/Firefox/Install.ps1";
. "$PSScriptRoot/../../Common/Scripts/Context.ps1";

$null = New-Module {
    . "$PSScriptRoot/../Scripts/Deployment.ps1";
    . "$PSScriptRoot/../Scripts/Hooks.ps1";
    . "$PSScriptRoot/../Scripts/PowerManagement.ps1";
    . "$PSScriptRoot/../Scripts/Registry.ps1";
    . "$PSScriptRoot/../Scripts/Security.ps1";
    . "$PSScriptRoot/../Scripts/Update.ps1";
    . "$PSScriptRoot/../Scripts/Users.ps1";
    . "$PSScriptRoot/../../Common/Scripts/Config.ps1";
    . "$PSScriptRoot/../../Common/Scripts/Operations.ps1";
    . "$PSScriptRoot/../../Common/Scripts/Software.ps1";
    . "$PSScriptRoot/../../Common/Scripts/SoftwareManagement.ps1";
    . "$PSScriptRoot/../../Common/Types/InstallerAction.ps1";

    <#
        .SYNOPSIS
        Finishes the installation of a running Windows machine.
    #>
    function Start-WindowsInstallation {
        Start-Operation -NoImplicitCleanup {
            Start-InstallationLoop;
        };
    }

    <#
        .SYNOPSIS
        Starts the installation loop.
    #>
    function Start-InstallationLoop {
        while (-not (Get-IsFinished)) {
            if (Test-Admin) {
                $null = Import-Module PSWindowsUpdate;

                Invoke-Hook "Invoke-WindowsUpdate" -Fallback {
                    Update-WindowsInstallation;
                };

                if ((Get-WURebootStatus -Silent)) {
                    Restart-Intermediate;
                    return;
                }
            }

            switch (Get-Stage) {
                ($null) {
                    Set-Stage ([SetupStage]::Configure);
                    break;
                }
                ([SetupStage]::Configure) {
                    if (Get-Config "valhalla.windows.dualboot.enable") {
                        if (-not (Test-Qemu)) {
                            # Fix synchronization between Linux and Windows clocks.
                            Set-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\TimeZoneInformation" -Name "RealTimeIsUniversal" -Value 1 -Type "DWord";
                        }

                        # Force time resynchronization
                        $timeZoneOption = "Start";
                        $timeZoneKey = "HKLM:\SYSTEM\CurrentControlSet\Services\tzautoupdate";
                        $service = Get-Service W32Time;
                        $autoUpdate = (Get-Item $timeZoneKey).GetValue($timeZoneOption);
                        $stopped = ($service.Status -eq "Stopped");

                        $setUpdate = { param([int] $Value) Set-ItemProperty $timeZoneKey -Name $timeZoneOption $Value };
                        & $setUpdate 3;
                        Start-Service $service;
                        w32tm /resync /force;
                        & $setUpdate $autoUpdate;

                        if ($stopped) {
                            Stop-Service $service;
                        }
                    }

                    Set-Stage ([SetupStage]::Install);
                }
                ([SetupStage]::Install) {
                    Write-Host "Entering install phase";
                    Deploy-SoftwareAction;
                    Set-Stage ([SetupStage]::CreateUser);
                }
                ([SetupStage]::CreateUser) {
                    $users = @(Get-Users);
                    $i = Get-CurrentUser;

                    for (; $i -lt $users.Count; $i++) {
                        $name = $users[$i];
                        $msAccount = Get-UserConfig -UserName $name "microsoftAccount";
                        Set-CurrentUser $i;

                        if (Test-Admin) {
                            Disable-BootMessage;
                        }

                        while ((Get-UserStage) -ne ([UserStage]::Completed)) {
                            switch (Get-UserStage) {
                                ($null) {
                                    Set-UserStage ([UserStage]::Create);
                                    continue;
                                }
                                ([UserStage]::Create) {
                                    if ($env:UserName -ne $name) {
                                        $userInfo = @{
                                            name = $name;
                                            msAccount = $msAccount;
                                        };

                                        New-ValhallaUser @userInfo;

                                        if ($msAccount) {
                                            logoff;
                                        } else {
                                            Restart-Intermediate;
                                        }

                                        exit;
                                    } else {
                                        if ($msAccount) {
                                            if (-not (Test-Admin)) {
                                                Invoke-OneShot DisableUAC;
                                                Restart-Intermediate -NoRegister;
                                                return;
                                            }

                                            Clear-SetupRegistration;
                                            Disable-OneShotListener;
                                        }

                                        Set-UserStage ([UserStage]::Configure);
                                    }
                                }
                                (([UserStage]::Configure)) {
                                    $displayName = Get-UserConfig -UserName $name "displayName";

                                    $userArguments = @{
                                        name = $name;
                                    };

                                    if ($displayName) {
                                        $userArguments.fullName = $displayName;
                                    }

                                    $adminGroup = @{
                                        SID = [SecurityIdentifier]::new([WellKnownSidType]::BuiltinAdministratorsSid, $null);
                                    };

                                    Set-LocalUser @userArguments;
                                    Deploy-SoftwareAction -Action ([InstallerAction]::ConfigureUser);
                                    Remove-LocalGroupMember -Member "$name" @adminGroup -ErrorAction SilentlyContinue;

                                    foreach ($group in Get-UserConfig -UserName "$name" "groups") {
                                        Add-LocalGroupMember -Member "$name" -Name "$group";
                                    }

                                    if (-not $msAccount) {
                                        net user $name /logonpasswordchg:yes;
                                    }

                                    Set-UserStage ([UserStage]::Cleanup);
                                }
                                ([UserStage]::Cleanup) {
                                    $user = Get-SetupUser;
                                    Disable-LocalUser $name;
                                    Enable-LocalUser $user;
                                    Set-AutologinUser $user;
                                    Unregister-WslDistribution;
                                    Set-UserStage ([UserStage]::Completed);
                                    Restart-Intermediate;
                                    exit;
                                }
                            }
                        }
                    }

                    foreach ($user in $users) {
                        Enable-LocalUser $user;
                    }

                    Set-IsFinished $true;
                }
            }
        }
    }

    function Invoke-WindowsInstallation([Context] $context) {
        $Global:InformationPreference = "Continue";
        $Global:ErrorActionPreference = "Inquire";
        $context.UserNames ??= @("Manuel");
        Start-OldWindowsInstallationScript $context;
    }

    function Start-OldWindowsInstallationScript([Context] $context) {
        . "$PSScriptRoot/Upgrade.ps1";
        $finished = $false;
        Remove-Item Env:\POSH_THEME -ErrorAction SilentlyContinue;
        $configPath = "$PSScriptRoot/../Config";
        $softwarePath = "$PSScriptRoot/../Software";
        $initialConfigStage = "InitialConfiguration";
        $prerequisitesStage = "InstallationPrerequisites";
        $driverStage = "DriverInstallation";
        $softwareStage = "MachineWideSoftwareInstallation";
        $userStage = "CreatingUser";
        $restorationStage = "Restoring";


        while (-not $finished) {
            switch ($context.GetStage()) {
                { (-not $_) -or ($_ -eq $initialConfigStage) } {
                    $context.SetStage($initialConfigStage);

                    if ((Get-Command Initialize-Configuration -ErrorAction SilentlyContinue)) {
                        Write-Information "Configuration initialization function was found. Running...";
                        Initialize-Configuration $context;
                    }

                    $null = Enable-WindowsOptionalFeature -Online -All -FeatureName "NetFx3";

                    . "$configPath/Windows/Install.ps1" $context;
                    . "$configPath/Explorer/Install.ps1" $context;
                    . "$configPath/OpenSSH/Install.ps1" $context;
                    . "$configPath/chocolatey/Install.ps1";

                    $context.RemoveDesktopIcon("*Microsoft Edge*");
                    $context.SetStage($prerequisitesStage);
                    break;
                }
                # Always install updates
                default {
                    Write-Host "Starting Installation and Restoration of Windows";
                    Update-WindowsInstallation $context;
                }
                $prerequisitesStage {
                    Write-Host "Installing prerequisites for installing software";

                    if (-not $(Get-Command winget)) {
                        choco install -y winget;
                    }

                    Install-Module -AcceptLicense -Force "NuGet";
                    Import-Module NuGet;

                    Install-Firefox $context;
                    choco install -y selenium-gecko-driver;
                    $null = Install-Package -Force Selenium.WebDriver -RequiredVersion 4.10.0 -SkipDependencies;

                    winget install --accept-source-agreements --accept-package-agreements -e --id AutoHotkey.AutoHotkey;

                    $context.SetStage($driverStage);
                    break;
                }
                $driverStage {
                    Write-Host "Installing drivers";

                    if ((Get-Command Install-PortValhallaDrivers -ErrorAction SilentlyContinue)) {
                        Write-Information "Driver installation function was found. Starting installation";
                        Install-PortValhallaDrivers $context;
                    }

                    Write-Information "Finished installing drivers";
                    $context.SetStage($softwareStage);
                    $context.Reboot();
                    exit;
                }
                $softwareStage {
                    Write-Host "Setting up software with default app associations";
                    . "$softwarePath/WinSCP/Install.ps1" $context;
                    . "$softwarePath/Thunderbird/Install.ps1" $context;

                    Write-Host "Installing default settings for new users";
                    . "$softwarePath/aliae/Install.ps1" $context;
                    . "$softwarePath/posh-git/Install.ps1";
                    . "$softwarePath/Terminal-Icons/Install.ps1";
                    . "$softwarePath/Oh My Posh/Install.ps1" $context;
                    $context.SetStage($userStage);
                    break;
                }
                $userStage {
                    Install-PersonalUsers $context;
                    $context.SetStage($restorationStage);
                    break;
                }
                $restorationStage {
                    Restore-WindowsInstallation $context;
                    $finished = $true;
                    break;
                }
            }
        }
    }
};