# define placeholders
$placeholderId  = "__ID__"
$placeholderIdBase64 = "__ID-BASE64__"
$placeholderUrl = "__URL__"
$placeholderHost = "__HOST__"
$placeholderDid = "__DID__"
$placeholderPort = "__PORT__"
$placeholderEnvPgPw = "__PG-ROOTPW__"
$placeholderEnvPgConnPw = "__PG-CONNPW__"
$placeholderEnvSuperuserKey = "__SU-KEY__"
$placeholderEnvIdApiKey = "__IDAPI-KEY__"
$placeholderEnvMgmtKey = "__MGMT-KEY__"

# define source and target directories
$sourceRoot = "connector-template"
$targetRoot = "_new-connector"


# function to ask a value from the user and test it for validity
# (allowed are only upper/lowercase letters, digits, - and _)
# If the user provides no value and min-length is > 0, a value is generated
function ReadValidatedValue {
    param(
        [string]$Prompt,
        [int]$MinLength = 0,
        [bool]$AutoGenerate = $false
    )
    while ($true) {
        $value = Read-Host $Prompt

        # Auto-generation
        if ($AutoGenerate -and $value -eq "") {
            if ($MinLength -gt 0) {
                Write-Host "- no input provided, generating a random value..."
                return NewRandomString -Length $MinLength
            }
        }
        # If autogeneration is OFF, empty input should not pass
        if (-not $AutoGenerate -and $value -eq "") {
            Write-Host "- input must not be empty." -ForegroundColor Red
            continue
        }

        # character check
        if ($value -notmatch '^[A-Za-z0-9_-]+$') {
            Write-Host "- invalid input: allowed are only letters, digits, '-' or '_'." -ForegroundColor Red
            continue
        }
        # length check only if MinLength > 0
        if ($MinLength -gt 0 -and $value.Length -lt $MinLength) {
            Write-Host "- input must be at least $MinLength characters long." -ForegroundColor Red
            continue
        }
        return $value
    }
}

# create a random string of letters and digits, cryptographically strong
function NewRandomString {
    param(
        [int]$Length = 16
    )

    if ($Length -le 0) {
        throw "- length must be greater than 0."
    }

    # Allowed characters: letters + digits
    $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.ToCharArray()

    # Cryptographically strong RNG
    $rng   = [System.Security.Cryptography.RandomNumberGenerator]::Create()
    $byte  = New-Object 'System.Byte[]' 1
    $sb    = New-Object System.Text.StringBuilder

    for ($i = 0; $i -lt $Length; $i++) {
        $rng.GetBytes($byte)
        $index = $byte[0] % $chars.Length
        $null  = $sb.Append($chars[$index])
    }

    $rng.Dispose()
    return $sb.ToString()
}

# fail early (before asking user for values) if source directory does not exist
if (-not (Test-Path $sourceRoot)) {
    Write-Host "Could not find source directory '$sourceRoot'." -ForegroundColor Red
    exit 1
}
# create target directory, if necessary
if (-not (Test-Path $targetRoot)) {
    New-Item -ItemType Directory -Path $targetRoot | Out-Null
}

Write-Host ""

# ask user for participant ID
$id = ReadValidatedValue -MinLength 8 -AutoGenerate $false -Prompt "Please enter the participant ID. You could use something like yourCompany-connector"

# ask user for URL and check it
do {
    $url = Read-Host "Enter the URL of your connector, starting with https://"
    # check URL (must be valid uri and https
    $isValid = [Uri]::IsWellFormedUriString($url, [System.UriKind]::Absolute) -and ([Uri]$url).Scheme -eq "https"
    if (-not $isValid) {
        Write-Host "- invalid URL, please try again (must be https)" -ForegroundColor Red
    }
} until ($isValid)

# ask user for portnumber and check it
do {
    $portInput = Read-Host "Enter the port number of your connector's nginx (1-65535, default 80)"

    if ([string]::IsNullOrWhiteSpace($portInput)) {
        $port = 80
        $isValid = $true
    }
    else {
        $port = 0
        $isInt = [int]::TryParse($portInput, [ref]$port)
        $isValid = $isInt -and ($port -ge 1) -and ($port -le 65535)
    }

    if (-not $isValid) {
        Write-Host "- Invalid port. Please enter a number between 1 and 65535." -ForegroundColor Red
    }
} until ($isValid)

# ask user for various passwords
$pgRootPw = ReadValidatedValue -Prompt "Password for Postgres root user (min length 20, empty = auto-generate)" -MinLength 20 -AutoGenerate $true
$pgConnPw = ReadValidatedValue -Prompt "Password for Postgres connector user (min length 20, empty = auto-generate)" -MinLength 20 -AutoGenerate $true
$superuserPw = ReadValidatedValue -Prompt "Enter password for super-user (min length 20, empty = auto-generate)" -MinLength 20 -AutoGenerate $true
$idApiKey = ReadValidatedValue -Prompt "Enter key for identity API (min length 20, empty = auto-generate)" -MinLength 20 -AutoGenerate $true
$mgmtApiKey = ReadValidatedValue -Prompt "Enter key for management API (min length 20, empty = auto-generate)" -MinLength 20 -AutoGenerate $true

# trim URL and convert URL to DID and to hostname (no protocol and path, used in nginx.conf)
$url = $url.TrimEnd('/')                        # remove trailing slash
$did = $url -replace '^https://', 'did:web:'    # replace https:// with did:web:
$did = $did -replace '/', ':'                   # replace slashes in the path with colons
$did = "${did}:did"                               # append :did (we need {} otherwise :did is interpreted as variable scope)
$uri = [System.Uri]$url
$hostname = $uri.Host                           # server name only (no protocol, no path)

# calculate other derived values
$encodedId = [Convert]::ToBase64String( [System.Text.Encoding]::UTF8.GetBytes($id) )
$suEncodedName = 'c3VwZXItdXNlcg=='  # this is the base64 encoded version of 'super-user'
$suEncodedPw = [Convert]::ToBase64String( [System.Text.Encoding]::UTF8.GetBytes($superuserPw) )
$suKey = "$suEncodedName.$suEncodedPw"

# now we have all values (except vault key, which we can only get later)
# --- show values and ask user of ok  --------------------------------

Write-Host ""
Write-Host "Please check the values:" -ForegroundColor Cyan
Write-Host "--------------------------------------------------"
Write-Host "- connector ID:   '$id'"
Write-Host "- connector URL:  '$url'"
Write-Host "- nginx port:     $port"
Write-Host "- (the Passwords will be written into file .env, you can get them from there after file generation)"
Write-Host "--------------------------------------------------"
Write-Host ""
$confirm = Read-Host "Are these values ok? (y/n)"

if ($confirm.ToLower() -ne "y" -and $confirm.ToLower() -ne "yes") {
    Write-Host "Cancelled by user." -ForegroundColor Yellow
    exit 0
}

# --- work on the files -------------------------------------------------------------

Write-Host ""
Write-Host "Generating files for your connector..." -ForegroundColor Cyan

# get absolute path of source dir (= source root)
$sourceRootFull = (Resolve-Path $sourceRoot).Path

# work on all files in the template dir, recursively
Get-ChildItem -Path $sourceRoot -Recurse -File | ForEach-Object {
    $sourceFile = $_.FullName
    # find relative path (everything after source root)
    $relativePath = $sourceFile.Substring($sourceRootFull.Length).TrimStart('\','/')
    # calculate target path (from target root and relative path)
    $targetFile = Join-Path $targetRoot $relativePath
    # make sure target folder exists
    $targetDir = Split-Path $targetFile -Parent
    if (-not (Test-Path $targetDir)) {
        New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
    }

    # check file ending and decide what to do:
    # do repacement of placeholders only for .yml, .conf, .sql, .env, .env.json
	$ext = [System.IO.Path]::GetExtension($sourceFile).ToLower()

	if ($ext -eq ".yml" -or
		$ext -eq ".yaml" -or
		$ext -eq ".conf" -or
		$ext -eq ".sql" -or
		$ext -eq ".env" -or
		$ext -eq ".json") {
			
        $content = Get-Content $sourceFile -Raw
        $content = $content.Replace($placeholderId, $id)
        $content = $content.Replace($placeholderUrl, $url)
        $content = $content.Replace($placeholderHost, $hostname)
        $content = $content.Replace($placeholderDid, $did)
        $content = $content.Replace($placeholderPort, $port)

        $content = $content.Replace($placeholderIdBase64, $encodedId)
        $content = $content.Replace($placeholderEnvPgPw, $pgRootPw)
        $content = $content.Replace($placeholderEnvPgConnPw, $pgConnPw)
        $content = $content.Replace($placeholderEnvSuperuserKey, $suKey)
        $content = $content.Replace($placeholderEnvIdApiKey, $idApiKey)
        $content = $content.Replace($placeholderEnvMgmtKey, $mgmtApiKey)

        $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
		[System.IO.File]::WriteAllText($targetFile, $content, $utf8NoBom)
        Write-Host "- done (with replacements):  $relativePath" -ForegroundColor Green
    }
    else {
        # the other files are only copied
        Copy-Item -Path $sourceFile -Destination $targetFile -Force
        Write-Host "- done (only copied):        $relativePath" -ForegroundColor DarkGray
    }

}

Write-Host ""
Write-Host "Finished! The new files are in folder '$targetRoot'." -ForegroundColor Cyan
Write-Host "The passwords are in file '.env'" -ForegroundColor Cyan
