#Requires -Version 5.1
param (
    [string]$BackendUrl  = "https://bdma.co.in/earnapp",
    [int]   $VMId        = 0,
    [string]$Group       = "Default",
    [string]$UnitId      = "DefaultUnit",
    [string]$RdpNickname = "UnknownRDP"
)

Set-StrictMode -Off
[System.Net.ServicePointManager]::DnsRefreshTimeout = 0
[System.Net.ServicePointManager]::DefaultConnectionLimit = 100
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$AGENT_VERSION            = "3.0.1"
$AgentDir                 = "$env:APPDATA\SinnerWatchdog"
$LogPath                  = "$AgentDir\agent.log"
$ConfigPath               = "$AgentDir\config.json"
$SelfPath                 = $MyInvocation.MyCommand.Path
$TaskName                 = "SinnerWatchdogAgent"

$HeartbeatInterval        = 60
$StatsSyncInterval        = 300
$CommandPollInterval      = 10
$UpdateCheckInterval      = 1800
$FileClearInterval        = 300
$MaxLogLines              = 800
$ConnectingRestartTimeout = 600

$connectingSince = $null

if (!(Test-Path $AgentDir)) { New-Item -ItemType Directory -Path $AgentDir -Force | Out-Null }
if (!(Test-Path $LogPath))  { New-Item -ItemType File -Path $LogPath -Force | Out-Null }

function Write-Log {
    param([string]$Msg, [string]$Level = "INFO")
    if (!(Test-Path $AgentDir)) { New-Item -ItemType Directory -Path $AgentDir -Force | Out-Null }
    $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
    $line = "[$timestamp] [$Level] $Msg"
    Add-Content -Path $LogPath -Value $line -Encoding UTF8
    Write-Host $line -ForegroundColor Cyan
}

function Invoke-FileCleanup {
    param([int]$CurrentId)
    try {
        Write-Log "Running Maintenance (Clear Logs/Config)..."
        Clear-Content $LogPath -ErrorAction SilentlyContinue
        Clear-Content $ConfigPath -ErrorAction SilentlyContinue
        Save-Config $CurrentId
        Write-Log "Maintenance Complete."
    } catch {
        Write-Log "Cleanup Error: $($_.Exception.Message)" "ERROR"
    }
}

function Invoke-Api {
    param(
        [string]$Uri,
        [string]$Method     = "GET",
        [string]$Body       = "",
        [int]   $TimeoutSec = 15,
        [int]   $Retries    = 3
    )
    for ($i = 0; $i -lt $Retries; $i++) {
        try {
            $p = @{ Uri=$Uri; Method=$Method; TimeoutSec=$TimeoutSec; DisableKeepAlive=$true; ErrorAction='Stop' }
            if ($Method -ne "GET" -and $Body) {
                $p.ContentType = "application/json"
                $p.Body        = $Body
            }
            return Invoke-RestMethod @p
        } catch {
            Write-Log "API Error ($Uri): $($_.Exception.Message)" "WARN"
            if ($i -lt ($Retries - 1)) {
                $wait = [Math]::Pow(2, $i) * 2
                Start-Sleep $wait
            } else { throw }
        }
    }
}

function Get-PublicIP {
    $sources = @("https://api.ipify.org?format=json", "https://checkip.amazonaws.com")
    foreach ($src in $sources) {
        try {
            $r = Invoke-RestMethod $src -TimeoutSec 6 -DisableKeepAlive
            $ip = if ($r.ip) { $r.ip } elseif ($r.query) { $r.query } else { "$r".Trim() }
            if ($ip -match '^\d{1,3}(\.\d{1,3}){3}$') { return $ip }
        } catch {}
    }
    return "0.0.0.0"
}

function Save-Config { param([int]$Id)
    @{
        vm_id   = $Id
        unit_id = $UnitId
        group   = $Group
        rdp     = $RdpNickname
        backend = $BackendUrl
        version = $AGENT_VERSION
    } | ConvertTo-Json | Out-File $ConfigPath -Force -Encoding UTF8
}

function Load-Config {
    try {
        if (Test-Path $ConfigPath) {
            $c = Get-Content $ConfigPath -Raw | ConvertFrom-Json
            return [int]$c.vm_id
        }
    } catch { Write-Log "Config Load Error: $($_.Exception.Message)" "ERROR" }
    return 0
}

function Invoke-SelfUpdate {
    param([switch]$Force)
    try {
        Write-Log "Checking for updates..."
        $updateUrl = $BackendUrl + "/api/vm/update_check.php?version=" + $AGENT_VERSION
        $info = Invoke-Api $updateUrl -TimeoutSec 10 -Retries 2

        if (-not $info.update_available -and -not $Force) {
            Write-Log "No update available."
            return
        }

        $newVer   = $info.version
        $expected = $info.sha256.ToUpper()
        $tempFile = "$env:TEMP\sw_agent_$newVer.ps1"

        Write-Log "Downloading update v$newVer..."
        $wc = New-Object System.Net.WebClient
        $wc.Headers.Add("User-Agent", "SinnerWatchdog/$AGENT_VERSION")
        $wc.DownloadFile("$BackendUrl/static/agent.ps1", $tempFile)

        $actual = (Get-FileHash $tempFile -Algorithm SHA256).Hash.ToUpper()
        if ($actual -ne $expected) {
            Write-Log "Update Hash Mismatch! Aborting." "ERROR"
            Remove-Item $tempFile -Force
            return
        }

        Copy-Item $tempFile $SelfPath -Force
        Remove-Item $tempFile -Force
        Stop-ScheduledTask  -TaskName $TaskName -ErrorAction SilentlyContinue
        Start-Sleep 2
        Start-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
        Write-Log "Agent updated to v$newVer and restarted."
        exit 0
    } catch {
        Write-Log "Update Failed: $($_.Exception.Message)" "ERROR"
    }
}

function Find-EarnAppWindow {
    try {
        Add-Type -AssemblyName UIAutomationClient, UIAutomationTypes -ErrorAction SilentlyContinue
        $root = [System.Windows.Automation.AutomationElement]::RootElement
        $cond = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::NameProperty, "EarnApp")
        return $root.FindFirst([System.Windows.Automation.TreeScope]::Children, $cond)
    } catch { return $null }
}

function Get-EarnAppStatusFromUI { param($Elements)
    foreach ($e in $Elements) {
        $t = $e.Current.Name
        if ($t -like "*Earning income*")          { return "Running"    }
        if ($t -like "*Blocked*")                 { return "Blocked"    }
        if ($t -like "*resource sharing paused*") { return "Paused"     }
        if ($t -like "*Resume earning*")          { return "Paused"     }
        if ($t -like "*Connecting*")              { return "Connecting" }
    }
    return $null
}

function Get-EarnAppToken {
    try {
        $path = "$env:LOCALAPPDATA\BrightData"
        if (!(Test-Path $path)) { return "Not Found" }
        $file = Get-ChildItem $path -File | Select-Object -First 1
        if (!$file) { return "Not Found" }
        $raw = (Get-Content $file.FullName -Raw).Trim()
        return if ($raw.Contains(":")) { ($raw -split ":")[0].Trim() } else { $raw }
    } catch { return "Error" }
}

function Start-EarnApp {
    Write-Log "Attempting to start EarnApp..."
    Stop-Process -Name EarnApp -Force -ErrorAction SilentlyContinue
    Start-Sleep 2
    try { Start-Process "C:\Program Files (x86)\EarnApp\EarnApp.exe" -ErrorAction Stop } catch { Write-Log "Failed to start exe" "ERROR" }
    Start-Sleep 35
}

function Get-EarnAppStats {
    $result = @{ balance=0; traffic="0B"; status="Online" }
    $win = Find-EarnAppWindow
    if (-not $win) {
        Write-Log "EarnApp window not found, restarting..."
        Start-EarnApp
        $win = Find-EarnAppWindow
    }

    if ($win) {
        try {
            $all = $win.FindAll([System.Windows.Automation.TreeScope]::Descendants, [System.Windows.Automation.Condition]::TrueCondition)
            foreach ($e in $all) {
                $t = $e.Current.Name
                if ($t -match '\$[\d\.]+') {
                    $v = [decimal]($t -replace '[^\d\.]', '')
                    if ($v -gt $result.balance) { $result.balance = $v }
                }
            }
            $s = Get-EarnAppStatusFromUI -Elements $all
            if ($s) { $result.status = $s }
        } catch { Write-Log "UI Read Error: $($_.Exception.Message)" "WARN" }
    }
    return $result
}

function Send-Stats { param([int]$Id, $S)
    try {
        $body = @{
            vm_id         = $Id
            unit_id       = $UnitId
            group_name    = $Group
            rdp_nickname  = $RdpNickname
            balance       = ("{0:F4}" -f [decimal]$S.balance)
            traffic       = $S.traffic
            status        = $S.status
            ip_address    = (Get-PublicIP)
            earnapp_token = (Get-EarnAppToken)
            agent_version = $AGENT_VERSION
        } | ConvertTo-Json
        $url = $BackendUrl + "/save_data.php"
        Invoke-Api $url -Method POST -TimeoutSec 15 -Body $body
        Write-Log "Stats Sent -> Status: $($S.status) | Balance: $($S.balance)"
    } catch {
        Write-Log "Send-Stats Failed: $($_.Exception.Message)" "ERROR"
    }
}

function Send-Heartbeat { param([int]$Id)
    try {
        $currentStatus = "Online"
        $win = Find-EarnAppWindow
        if ($win) {
            $all = $win.FindAll([System.Windows.Automation.TreeScope]::Descendants, [System.Windows.Automation.Condition]::TrueCondition)
            $s = Get-EarnAppStatusFromUI -Elements $all
            if ($s) { $currentStatus = $s }
        }

        $body = @{
            action        = "heartbeat"
            vm_id         = $Id
            ip_address    = (Get-PublicIP)
            status        = $currentStatus
            agent_version = $AGENT_VERSION
        } | ConvertTo-Json
        $url = $BackendUrl + "/save_data.php"
        Invoke-Api $url -Method POST -TimeoutSec 10 -Body $body
        Write-Log "Heartbeat Sent (Status: $currentStatus)"
    } catch {
        Write-Log "Heartbeat Failed: $($_.Exception.Message)" "ERROR"
    }
}

function Invoke-AckCommand { param([int]$CmdId)
    try {
        $url = $BackendUrl + "/pages/api.php?action=ack_command" + "&id=" + $CmdId + "&vm_id=" + $CurrentVmId
        Invoke-Api $url -TimeoutSec 5
    } catch {}
}

function Handle-Command { param($Cmd)
    Write-Log "Executing Command: $($Cmd.action)"
    switch ($Cmd.action) {
        "stop_agent" {
            Invoke-AckCommand $Cmd.id
            Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
            Write-Log "Agent stopping per command."
            exit 0
        }
        "restart_vm" {
            Invoke-AckCommand $Cmd.id
            Write-Log "Restarting VM..."
            Start-Process "shutdown.exe" -ArgumentList "/r /t 5 /f" -WindowStyle Hidden
        }
        "fetch_balance" {
            $s = Get-EarnAppStats
            Send-Stats $CurrentVmId $s
            Invoke-AckCommand $Cmd.id
        }
        "restart_earnapp" {
            Start-EarnApp
            Invoke-AckCommand $Cmd.id
        }
        default { Invoke-AckCommand $Cmd.id }
    }
}

function Invoke-ConnectingWatchdog { param([string]$Status)
    if ($Status -eq "Connecting") {
        if ($null -eq $script:connectingSince) {
            $script:connectingSince = Get-Date
        } elseif (((Get-Date) - $script:connectingSince).TotalSeconds -ge $ConnectingRestartTimeout) {
            Write-Log "Connecting for $([int]((Get-Date) - $script:connectingSince).TotalSeconds)s, restarting EarnApp..." "WARN"
            Start-EarnApp
            $script:connectingSince = $null
        }
    } else {
        $script:connectingSince = $null
    }
}

$CurrentVmId = if ($VMId -gt 0) { $VMId } else { Load-Config }

if (-not $CurrentVmId -or $CurrentVmId -eq 0) {
    Write-Log "No Valid VM ID. Starting Registration..."
    while (-not $CurrentVmId) {
        try {
            $body = @{
                group_name    = $Group
                rdp_nickname  = $RdpNickname
                unit_id       = $UnitId
                assigned_ip   = (Get-PublicIP)
                earnapp_token = (Get-EarnAppToken)
                agent_version = $AGENT_VERSION
            } | ConvertTo-Json
            $r = Invoke-Api "$BackendUrl/api/vm/register.php" -Method POST -Body $body
            if ($r.success) {
                $CurrentVmId = [int]$r.vm_id
                Save-Config $CurrentVmId
                Write-Log "Registration SUCCESS. VM ID: $CurrentVmId"
            } else {
                Write-Log "Registration Failed: $($r.error)" "WARN"
            }
        } catch {
            Write-Log "Registration Exception: $($_.Exception.Message)" "ERROR"
            Start-Sleep 10
        }
    }
}

Write-Log "Agent v$AGENT_VERSION Initialized. VM ID: $CurrentVmId"
Invoke-SelfUpdate

$lastHeartbeat   = [DateTime]::MinValue
$lastStatsSync   = [DateTime]::MinValue
$lastCommandPoll = [DateTime]::MinValue
$lastUpdateCheck = Get-Date
$lastFileClear   = Get-Date

while ($true) {
    Start-Sleep 5
    $now = Get-Date

    if (($now - $lastFileClear).TotalSeconds -ge $FileClearInterval) {
        Invoke-FileCleanup $CurrentVmId
        $lastFileClear = $now
    }

    if (($now - $lastCommandPoll).TotalSeconds -ge $CommandPollInterval) {
        try {
            $cmd = Invoke-Api "$BackendUrl/pages/api.php?action=fetch_command&vm_id=$CurrentVmId" -TimeoutSec 5
            if ($cmd -and $cmd.action) { Handle-Command $cmd }
        } catch {
            Write-Log "Command Poll Error: $($_.Exception.Message)" "WARN"
        }
        $lastCommandPoll = $now
    }

    if (($now - $lastHeartbeat).TotalSeconds -ge $HeartbeatInterval) {
        Send-Heartbeat $CurrentVmId
        $lastHeartbeat = $now
    }

    if (($now - $lastStatsSync).TotalSeconds -ge $StatsSyncInterval) {
        $s = Get-EarnAppStats
        Send-Stats $CurrentVmId $s
        $lastStatsSync = $now
    }

    if (($now - $lastUpdateCheck).TotalSeconds -ge $UpdateCheckInterval) {
        Invoke-SelfUpdate
        $lastUpdateCheck = $now
    }

    $currentStatus = "Online"
    $w = Find-EarnAppWindow
    if ($w) {
        $el = $w.FindAll([System.Windows.Automation.TreeScope]::Descendants, [System.Windows.Automation.Condition]::TrueCondition)
        $s  = Get-EarnAppStatusFromUI -Elements $el
        if ($s) { $currentStatus = $s }
    }
    Invoke-ConnectingWatchdog -Status $currentStatus
}