param(
    [Alias('ServerHost')]
    [string]$SshHost = '192.168.0.185',
    [Alias('Username')]
    [string]$SshUsername = 'sevenhill',
    [Alias('Password')]
    [Parameter(Mandatory = $true)]
    [string]$SshPassword,
    [Alias('BaseUrl')]
    [string]$PublicBaseUrl = 'https://argus.kusoft.xyz',
    [string]$ArgusDataPath = '/srv/argus-data',
    [Parameter(Mandatory = $true)]
    [ValidatePattern('^[a-z0-9][a-z0-9-]{0,99}$')]
    [string]$Slug,
    [Parameter(Mandatory = $true)]
    [string]$Name,
    [Parameter(Mandatory = $true)]
    [string]$Summary,
    [Parameter(Mandatory = $true)]
    [string]$Description,
    [string]$AndroidPackageName,
    [Parameter(Mandatory = $true)]
    [string]$Version,
    [ValidateRange(0, [int]::MaxValue)]
    [int]$AndroidVersionCode = 0,
    [Parameter(Mandatory = $true)]
    [string]$PackagePath,
    [string]$Channel = 'stable',
    [string]$Platform = 'android',
    [string]$PackageKind = 'apk',
    [string]$Notes,
    [string]$RepositoryUrl,
    [string]$HomepageUrl = 'https://ikar.kusoft.xyz',
    [switch]$Unlisted
)

$ErrorActionPreference = 'Stop'

$resolvedPackagePath = (Resolve-Path -LiteralPath $PackagePath).Path
$packageItem = Get-Item -LiteralPath $resolvedPackagePath
if ($packageItem.Length -le 0) {
    throw "Package file is empty: $resolvedPackagePath"
}

$baseUrl = $PublicBaseUrl.TrimEnd('/')
$manifestUrl = "{0}/api/apps/{1}/manifest?platform={2}&channel={3}" -f `
    $baseUrl,
    [uri]::EscapeDataString($Slug),
    [uri]::EscapeDataString($Platform),
    [uri]::EscapeDataString($Channel)

$payload = [ordered]@{
    sshHost = $SshHost
    sshUsername = $SshUsername
    argusDataPath = $ArgusDataPath
    publicBaseUrl = $baseUrl
    packagePath = $resolvedPackagePath
    slug = $Slug
    name = $Name
    summary = $Summary
    description = $Description
    repositoryUrl = if ([string]::IsNullOrWhiteSpace($RepositoryUrl)) { $null } else { $RepositoryUrl.Trim() }
    homepageUrl = if ([string]::IsNullOrWhiteSpace($HomepageUrl)) { $null } else { $HomepageUrl.Trim() }
    isListed = (-not $Unlisted)
    version = $Version
    channel = $Channel
    platform = $Platform
    packageKind = $PackageKind
    notes = if ([string]::IsNullOrWhiteSpace($Notes)) { $null } else { $Notes }
} | ConvertTo-Json -Depth 6 -Compress

$env:IKAR_ARGUS_PUBLISH_PAYLOAD = $payload
$env:IKAR_ARGUS_PUBLISH_SSH_PASSWORD = $SshPassword

$pythonScript = @'
import hashlib
import json
import mimetypes
import os
import posixpath
import re
import shutil
import sqlite3
import sys
import uuid
from datetime import datetime, timezone
from pathlib import Path

try:
    import paramiko
except ImportError as exc:
    raise SystemExit("Python package 'paramiko' is required for SSH publication.") from exc

payload = json.loads(os.environ["IKAR_ARGUS_PUBLISH_PAYLOAD"])
ssh_password = os.environ["IKAR_ARGUS_PUBLISH_SSH_PASSWORD"]

host = payload["sshHost"]
username = payload["sshUsername"]
local_package = Path(payload["packagePath"]).resolve()
remote_upload_dir = f"/home/{username}/.argus-upload/{uuid.uuid4().hex}"
remote_package = posixpath.join(remote_upload_dir, local_package.name)


def ensure_remote_dir(sftp, path):
    current = "/"
    for part in [p for p in path.strip("/").split("/") if p]:
        current = posixpath.join(current, part)
        try:
            sftp.stat(current)
        except FileNotFoundError:
            sftp.mkdir(current)


def cleanup_upload(client):
    try:
        sftp = client.open_sftp()
        try:
            sftp.remove(remote_package)
        except FileNotFoundError:
            pass
        try:
            sftp.rmdir(remote_upload_dir)
        except OSError:
            pass
        sftp.close()
    except Exception:
        pass


remote_payload = dict(payload)
remote_payload["sourcePath"] = remote_package

remote_body = r"""
source = Path(PAYLOAD["sourcePath"]).resolve()
data_root = Path(PAYLOAD.get("argusDataPath", "/srv/argus-data")).resolve()
db_path = data_root / "argus.db"
packages_root = data_root / "Packages"

slug = PAYLOAD["slug"].strip()
name = PAYLOAD["name"].strip()
summary = PAYLOAD["summary"].strip()
description = PAYLOAD["description"].strip()
repository_url = (PAYLOAD.get("repositoryUrl") or "").strip() or None
homepage_url = (PAYLOAD.get("homepageUrl") or "").strip() or None
is_listed = 1 if PAYLOAD.get("isListed", True) else 0

version = PAYLOAD["version"].strip()
channel = (PAYLOAD.get("channel") or "stable").strip().lower()
platform = (PAYLOAD.get("platform") or "generic").strip().lower()
package_kind = (PAYLOAD.get("packageKind") or "binary").strip().lower()
notes = (PAYLOAD.get("notes") or "").strip() or None

if not re.fullmatch(r"[a-z0-9][a-z0-9-]{0,99}", slug):
    raise SystemExit("ARGUS_SLUG must contain only lowercase letters, digits, and hyphens, max 100 chars.")
if not source.is_file():
    raise SystemExit(f"SOURCE_FILE does not exist: {source}")
if not db_path.is_file():
    raise SystemExit(f"Argus database does not exist: {db_path}")
for key, value in {
    "ARGUS_NAME": name,
    "ARGUS_SUMMARY": summary,
    "ARGUS_DESCRIPTION": description,
    "ARGUS_VERSION": version,
}.items():
    if not value:
        raise SystemExit(f"{key} is required.")

release_id = str(uuid.uuid4()).upper()
now = datetime.now(timezone.utc).isoformat(timespec="microseconds")
safe_version = re.sub(r"[^a-zA-Z0-9._-]+", "-", version).strip("-._") or "release"
extension = source.suffix or ".bin"
stored_name = f"{datetime.now(timezone.utc):%Y%m%d%H%M%S}-{safe_version}-{release_id.replace('-', '')}{extension}"
stored_relative_path = f"{slug}/{stored_name}"
target_dir = packages_root / slug
target_path = target_dir / stored_name

target_dir.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, target_path)

size_bytes = target_path.stat().st_size
sha256 = hashlib.sha256(target_path.read_bytes()).hexdigest()
content_type = mimetypes.guess_type(source.name)[0] or "application/octet-stream"
if source.suffix.lower() == ".apk":
    content_type = "application/vnd.android.package-archive"

conn = sqlite3.connect(db_path)
try:
    conn.execute("PRAGMA foreign_keys = ON")
    conn.execute("BEGIN")

    row = conn.execute('SELECT "Id" FROM "Apps" WHERE "Slug" = ?', (slug,)).fetchone()
    if row is None:
        app_id = str(uuid.uuid4()).upper()
        conn.execute(
            '''
            INSERT INTO "Apps"
                ("Id", "Slug", "Name", "Summary", "Description", "RepositoryUrl", "HomepageUrl", "IsListed", "CreatedAt", "UpdatedAt")
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ''',
            (app_id, slug, name, summary, description, repository_url, homepage_url, is_listed, now, now),
        )
    else:
        app_id = row[0]
        conn.execute(
            '''
            UPDATE "Apps"
            SET "Name" = ?,
                "Summary" = ?,
                "Description" = ?,
                "RepositoryUrl" = ?,
                "HomepageUrl" = ?,
                "IsListed" = ?,
                "UpdatedAt" = ?
            WHERE "Id" = ?
            ''',
            (name, summary, description, repository_url, homepage_url, is_listed, now, app_id),
        )

    duplicate = conn.execute(
        '''
        SELECT "Id"
        FROM "Releases"
        WHERE "CatalogAppId" = ? AND "Version" = ? AND "Channel" = ? AND "Platform" = ?
        ''',
        (app_id, version, channel, platform),
    ).fetchone()
    if duplicate is not None:
        raise RuntimeError(f"Release already exists for {slug} {version} {channel} {platform}.")

    conn.execute(
        '''
        INSERT INTO "Releases"
            ("Id", "CatalogAppId", "Version", "Channel", "Platform", "PackageKind",
             "OriginalFileName", "StoredRelativePath", "ContentType", "PackageSizeBytes",
             "Sha256", "Notes", "PublishedAt")
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ''',
        (
            release_id,
            app_id,
            version,
            channel,
            platform,
            package_kind,
            source.name,
            stored_relative_path,
            content_type,
            size_bytes,
            sha256,
            notes,
            now,
        ),
    )

    conn.commit()
except Exception:
    conn.rollback()
    try:
        target_path.unlink()
    except FileNotFoundError:
        pass
    raise
finally:
    conn.close()

print(json.dumps({
    "releaseId": release_id,
    "slug": slug,
    "version": version,
    "channel": channel,
    "platform": platform,
    "storedRelativePath": stored_relative_path,
    "sizeBytes": size_bytes,
    "sha256": sha256,
    "manifestUrl": f"{PAYLOAD['publicBaseUrl']}/api/apps/{slug}/manifest?platform={platform}&channel={channel}",
}, ensure_ascii=False))
"""

remote_script = (
    "import json\n"
    "PAYLOAD = json.loads(" + json.dumps(json.dumps(remote_payload, ensure_ascii=False), ensure_ascii=False) + ")\n"
    "import hashlib\n"
    "import mimetypes\n"
    "import re\n"
    "import shutil\n"
    "import sqlite3\n"
    "import uuid\n"
    "from datetime import datetime, timezone\n"
    "from pathlib import Path\n"
    + remote_body
)

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(host, username=username, password=ssh_password, timeout=15)

try:
    sftp = client.open_sftp()
    ensure_remote_dir(sftp, remote_upload_dir)
    sftp.put(str(local_package), remote_package)
    sftp.close()

    stdin, stdout, stderr = client.exec_command("sudo -n true", timeout=30)
    stdout.read()
    stderr.read()
    can_sudo_without_password = stdout.channel.recv_exit_status() == 0

    if can_sudo_without_password:
        command = "sudo -n python3 -"
        input_text = remote_script
    else:
        command = "sudo -S -p '' python3 -"
        input_text = ssh_password + "\n" + remote_script

    stdin, stdout, stderr = client.exec_command(command, timeout=900)
    stdin.write(input_text)
    stdin.channel.shutdown_write()
    out = stdout.read().decode("utf-8", errors="replace")
    err = stderr.read().decode("utf-8", errors="replace")
    exit_status = stdout.channel.recv_exit_status()
    if exit_status != 0:
        raise RuntimeError(
            f"Remote Argus publication failed with exit status {exit_status}\n"
            f"STDOUT:\n{out}\nSTDERR:\n{err}"
        )
    print(out.strip())
finally:
    cleanup_upload(client)
    client.close()
'@

try {
    $publishOutput = $pythonScript | python -
    $pythonExitCode = $LASTEXITCODE
} finally {
    Remove-Item Env:\IKAR_ARGUS_PUBLISH_PAYLOAD -ErrorAction SilentlyContinue
    Remove-Item Env:\IKAR_ARGUS_PUBLISH_SSH_PASSWORD -ErrorAction SilentlyContinue
}

if ($pythonExitCode -ne 0) {
    throw "Argus SSH publication failed with exit code $pythonExitCode."
}

if ($publishOutput) {
    $publishOutput | Write-Output
}

$publishJson = $publishOutput |
    Where-Object { $_ -and $_.TrimStart().StartsWith('{') } |
    Select-Object -Last 1
if (-not $publishJson) {
    throw 'Argus publication did not return a JSON result.'
}
$publishResult = $publishJson | ConvertFrom-Json

$manifest = $null
$lastManifestError = $null
for ($attempt = 1; $attempt -le 6; $attempt++) {
    try {
        $manifest = Invoke-RestMethod -Method Get -Uri $manifestUrl -TimeoutSec 60
        if ($manifest.release.version -eq $Version) {
            break
        }
    } catch {
        $lastManifestError = $_
    }
    Start-Sleep -Seconds 2
}

if ($null -eq $manifest) {
    throw "Could not verify Argus public manifest at $manifestUrl. Last error: $lastManifestError"
}
if ($manifest.release.version -ne $Version) {
    throw "Argus public manifest is reachable, but latest version is $($manifest.release.version), expected $Version."
}
if ($manifest.release.sha256 -and $publishResult.sha256 -and
    -not $manifest.release.sha256.Equals($publishResult.sha256, [System.StringComparison]::OrdinalIgnoreCase)
) {
    throw "Argus public manifest SHA-256 does not match the published file."
}

$downloadPath = [string]$manifest.release.downloadPath
if ([string]::IsNullOrWhiteSpace($downloadPath)) {
    throw 'Argus public manifest does not contain release.downloadPath.'
}
$downloadUrl = if ($downloadPath.StartsWith('http://', [System.StringComparison]::OrdinalIgnoreCase) -or
    $downloadPath.StartsWith('https://', [System.StringComparison]::OrdinalIgnoreCase)
) {
    $downloadPath
} else {
    $baseUrl + '/' + $downloadPath.TrimStart('/')
}

$downloadResponse = $null
try {
    $downloadRequest = [System.Net.HttpWebRequest]::Create($downloadUrl)
    $downloadRequest.Method = 'GET'
    $downloadRequest.Timeout = 60000
    $downloadRequest.AddRange(0, 0)
    $downloadResponse = [System.Net.HttpWebResponse]$downloadRequest.GetResponse()
    $downloadStatus = [int]$downloadResponse.StatusCode
    if ($downloadStatus -ne 200 -and $downloadStatus -ne 206) {
        throw "Argus download endpoint returned HTTP $downloadStatus."
    }
} catch {
    throw "Could not verify Argus download endpoint at $downloadUrl. $($_.Exception.Message)"
} finally {
    if ($null -ne $downloadResponse) {
        $downloadResponse.Dispose()
    }
}

Write-Output "Verified manifest: $manifestUrl"
Write-Output "Verified download: $downloadUrl"
