from __future__ import annotations

import hashlib
import os
from pathlib import Path
import shutil
import subprocess
import time
import zipfile

from . import __version__


class AgentUpdater:
    def __init__(self, config, client, logger, install_dir: Path):
        self.config = config
        self.client = client
        self.logger = logger
        self.install_dir = install_dir.resolve()
        self.last_check = 0.0
        self.busy = False

    def maybe_update(self, force: bool = False) -> bool:
        if not self.config.auto_update or self.busy:
            return False
        now = time.time()
        if not force and now - self.last_check < self.config.update_check_interval_seconds:
            return False
        self.last_check = now
        try:
            manifest = self.client.update_manifest()
            latest = str(manifest.get("latest_version") or "").strip()
            if not latest or latest == __version__:
                return False
            expected = str(manifest.get("sha256") or "").lower().strip()
            package = self.client.download_update_package()
            actual = hashlib.sha256(package).hexdigest().lower()
            if len(expected) != 64 or actual != expected:
                raise RuntimeError("ERP Local Agent update package SHA256 verification failed.")
            self._stage_and_restart(latest, package)
            return True
        except Exception as exc:
            self.logger.exception("ERP Local Agent update check failed: %s", exc)
            return False

    def _stage_and_restart(self, latest: str, package: bytes) -> None:
        self.busy = True
        updates = self.install_dir / "updates"
        updates.mkdir(parents=True, exist_ok=True)
        safe = "".join(ch for ch in latest if ch.isalnum() or ch in ".-_") or "update"
        package_path = updates / f"ERP_Local_Agent_{safe}.zip"
        package_path.write_bytes(package)
        staged = updates / f"staged_{safe}"
        shutil.rmtree(staged, ignore_errors=True)
        staged.mkdir(parents=True, exist_ok=True)
        with zipfile.ZipFile(package_path, "r") as archive:
            archive.extractall(staged)
        if not (staged / "erp_local_agent" / "__init__.py").exists():
            raise RuntimeError("ERP Local Agent update package is incomplete.")
        script_path = updates / f"apply_{safe}.ps1"
        script_path.write_text(self._powershell_update_script(staged), encoding="utf-8")
        flags = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0) | getattr(subprocess, "DETACHED_PROCESS", 0)
        subprocess.Popen(
            ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", str(script_path), "-ParentPid", str(os.getpid())],
            cwd=str(self.install_dir), creationflags=flags, close_fds=True,
        )
        self.logger.warning("ERP Local Agent update %s staged; restarting.", latest)
        os._exit(0)

    def _powershell_update_script(self, staged: Path) -> str:
        install = str(self.install_dir).replace("'", "''")
        stage = str(staged.resolve()).replace("'", "''")
        lines = [
            "param([int]$ParentPid)",
            '$ErrorActionPreference = "Stop"',
            "$InstallDir = '" + install + "'",
            "$StagedDir = '" + stage + "'",
            "$TaskName = 'ERP Local Agent'",
            "$BackupDir = Join-Path $InstallDir ('updates\\backup_' + (Get-Date -Format 'yyyyMMdd_HHmmss'))",
            "try {",
            "  if ($ParentPid -gt 0) { try { Wait-Process -Id $ParentPid -Timeout 120 -ErrorAction SilentlyContinue } catch {} }",
            "  try { Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue } catch {}",
            "  New-Item -ItemType Directory -Force -Path $BackupDir | Out-Null",
            "  if (Test-Path (Join-Path $InstallDir 'erp_local_agent')) { Copy-Item (Join-Path $InstallDir 'erp_local_agent') -Destination $BackupDir -Recurse -Force }",
            "  Remove-Item (Join-Path $InstallDir 'erp_local_agent') -Recurse -Force -ErrorAction SilentlyContinue",
            "  Copy-Item (Join-Path $StagedDir 'erp_local_agent') -Destination $InstallDir -Recurse -Force",
            "  foreach ($Item in @('requirements.txt','run_agent.bat','run_once.bat','check_config.py','check_config.bat','start_task_scheduler.bat','stop_task_scheduler.bat','status_task_scheduler.bat','install_task_scheduler.bat','uninstall_task_scheduler.bat')) { $Source=Join-Path $StagedDir $Item; if (Test-Path $Source) { Copy-Item $Source -Destination (Join-Path $InstallDir $Item) -Force } }",
            "  $Python = Join-Path $InstallDir '.venv\\Scripts\\python.exe'",
            "  if (-not (Test-Path $Python)) { throw 'ERP Local Agent virtual environment is missing.' }",
            "  & $Python -m pip install -r (Join-Path $InstallDir 'requirements.txt')",
            "  if ($LASTEXITCODE -ne 0) { throw 'Dependency update failed.' }",
            "  Start-ScheduledTask -TaskName $TaskName",
            "} catch {",
            "  try { if (Test-Path (Join-Path $BackupDir 'erp_local_agent')) { Remove-Item (Join-Path $InstallDir 'erp_local_agent') -Recurse -Force -ErrorAction SilentlyContinue; Copy-Item (Join-Path $BackupDir 'erp_local_agent') -Destination $InstallDir -Recurse -Force }; Start-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue } catch {}",
            "  New-Item -ItemType Directory -Force -Path (Join-Path $InstallDir 'logs') | Out-Null",
            "  Add-Content -Path (Join-Path $InstallDir 'logs\\update_error.log') -Value ((Get-Date).ToString('s') + ' ' + $_.Exception.Message)",
            "  exit 1",
            "}",
        ]
        return "\r\n".join(lines) + "\r\n"
