<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Controller;

use OCA\Mitgliederverwaltung\Service\BackupService;
use OCA\Mitgliederverwaltung\Service\BackupSettingsService;
use OCA\Mitgliederverwaltung\Service\SelfUpdateService;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataDownloadResponse;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use Psr\Log\LoggerInterface;

/**
 * REST API controller for backup and restore operations.
 *
 * All endpoints are admin-only (enforced by AuthorizationMiddleware).
 */
class BackupController extends ApiController {

    use ApiControllerTrait;

    private BackupService $backupService;
    private BackupSettingsService $settingsService;
    private SelfUpdateService $updateService;
    private LoggerInterface $logger;

    public function __construct(
        string $appName,
        IRequest $request,
        BackupService $backupService,
        BackupSettingsService $settingsService,
        SelfUpdateService $updateService,
        LoggerInterface $logger
    ) {
        parent::__construct($appName, $request);
        $this->backupService = $backupService;
        $this->settingsService = $settingsService;
        $this->updateService = $updateService;
        $this->logger = $logger;
    }

    /**
     * List all available backups.
     *
     * GET /api/v1/backups
     */
    public function index(): JSONResponse {
        return $this->handleAction(function () {
            return new JSONResponse($this->backupService->listBackups());
        }, 'Backup-Liste');
    }

    /**
     * Create a new backup.
     *
     * POST /api/v1/backups
     * Body: { password?: string }
     */
    public function create(): JSONResponse {
        return $this->handleAction(function () {
            $data = $this->getRequestData();
            $password = $data['password'] ?? null;
            $result = $this->backupService->createBackup($password, 'manual');
            return new JSONResponse($result, Http::STATUS_CREATED);
        }, 'Backup erstellen');
    }

    /**
     * Get backup info (manifest).
     *
     * GET /api/v1/backups/{filename}
     */
    public function show(string $filename): JSONResponse {
        return $this->handleAction(function () use ($filename) {
            $info = $this->backupService->getBackupInfo($filename);
            if ($info === null) {
                return new JSONResponse(
                    ['error' => 'Backup nicht gefunden'],
                    Http::STATUS_NOT_FOUND
                );
            }
            return new JSONResponse($info);
        }, 'Backup-Info');
    }

    /**
     * Download a backup ZIP file.
     *
     * GET /api/v1/backups/{filename}/download
     */
    public function download(string $filename): DataDownloadResponse|JSONResponse {
        try {
            $filePath = $this->backupService->getBackupFilePath($filename);
            $content = file_get_contents($filePath);
            return new DataDownloadResponse(
                $content,
                basename($filename),
                'application/zip'
            );
        } catch (\Exception $e) {
            $this->logger->error('Backup download failed', [
                'filename' => $filename,
                'exception' => $e,
                'app' => 'mitgliederverwaltung',
            ]);
            return new JSONResponse(
                ['error' => 'Download fehlgeschlagen'],
                Http::STATUS_INTERNAL_SERVER_ERROR
            );
        }
    }

    /**
     * Restore from a backup.
     *
     * POST /api/v1/backups/{filename}/restore
     * Body: { password?: string, confirm: true }
     */
    public function restore(string $filename): JSONResponse {
        return $this->handleAction(function () use ($filename) {
            $data = $this->getRequestData();
            if (empty($data['confirm'])) {
                return new JSONResponse(
                    ['error' => 'Bestaetigung erforderlich (confirm: true)'],
                    Http::STATUS_BAD_REQUEST
                );
            }
            $password = $data['password'] ?? null;
            $result = $this->backupService->restoreBackup($filename, $password);
            return new JSONResponse($result);
        }, 'Backup wiederherstellen');
    }

    /**
     * Delete a backup.
     *
     * DELETE /api/v1/backups/{filename}
     */
    public function destroy(string $filename): JSONResponse {
        return $this->handleAction(function () use ($filename) {
            $this->backupService->deleteBackup($filename);
            return new JSONResponse(['status' => 'deleted']);
        }, 'Backup loeschen');
    }

    /**
     * Get backup settings.
     *
     * GET /api/v1/backups/settings
     */
    public function getSettings(): JSONResponse {
        return new JSONResponse($this->settingsService->getAllSettings());
    }

    /**
     * Update backup settings.
     *
     * PUT /api/v1/backups/settings
     * Body: { schedule?, retentionCount?, password? }
     */
    public function updateSettings(): JSONResponse {
        return $this->handleAction(function () {
            $data = $this->getRequestData();
            $this->settingsService->updateSettings($data);
            return new JSONResponse($this->settingsService->getAllSettings());
        }, 'Backup-Einstellungen aktualisieren');
    }

    // ── Self-update endpoints ───────────────────────────────────

    /**
     * Check for available updates from Gitea.
     *
     * GET /api/v1/update/check
     */
    public function checkUpdate(): JSONResponse {
        try {
            return new JSONResponse($this->updateService->checkForUpdate());
        } catch (\Exception $e) {
            $this->logger->error('Update-Check fehlgeschlagen', [
                'exception' => $e,
                'app' => 'mitgliederverwaltung',
            ]);
            return new JSONResponse(
                ['error' => 'Update-Check fehlgeschlagen: ' . $e->getMessage()],
                Http::STATUS_INTERNAL_SERVER_ERROR
            );
        }
    }

    /**
     * Download and install the latest update.
     *
     * POST /api/v1/update/install
     * Body: { force?: bool }
     */
    public function installUpdate(): JSONResponse {
        try {
            $data = $this->getRequestData();
            $force = !empty($data['force']);
            $result = $this->updateService->performUpdate($force);
            return new JSONResponse($result);
        } catch (\Exception $e) {
            $this->logger->error('Update-Installation fehlgeschlagen', [
                'exception' => $e,
                'app' => 'mitgliederverwaltung',
            ]);
            return new JSONResponse(
                ['error' => 'Update-Installation fehlgeschlagen: ' . $e->getMessage()],
                Http::STATUS_INTERNAL_SERVER_ERROR
            );
        }
    }

    /**
     * Install a manually-uploaded release archive.
     *
     * POST /api/v1/update/install-manual (multipart/form-data)
     *   - archive: <file>  (the .tar.gz)
     *   - signature: <file>  (the matching .tar.gz.sig, 64 bytes Ed25519)
     *
     * The signature is verified against the hardcoded public key before
     * extraction. Uploads exceeding {@see MANUAL_UPLOAD_MAX_BYTES} are
     * rejected outright.
     */
    public function installUpdateManual(): JSONResponse {
        try {
            $archive = $this->request->getUploadedFile('archive');
            $signature = $this->request->getUploadedFile('signature');

            if ($archive === null || !is_array($archive) || ($archive['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
                return new JSONResponse(
                    ['error' => 'Feld "archive" (.tar.gz) fehlt oder Upload fehlgeschlagen.'],
                    Http::STATUS_BAD_REQUEST
                );
            }
            if ($signature === null || !is_array($signature) || ($signature['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
                return new JSONResponse(
                    ['error' => 'Feld "signature" (.tar.gz.sig) fehlt oder Upload fehlgeschlagen.'],
                    Http::STATUS_BAD_REQUEST
                );
            }

            if (($archive['size'] ?? 0) > self::MANUAL_UPLOAD_MAX_BYTES) {
                return new JSONResponse(
                    ['error' => 'Archiv zu gross (Maximum: ' . self::MANUAL_UPLOAD_MAX_BYTES . ' Bytes).'],
                    Http::STATUS_PAYLOAD_TOO_LARGE
                );
            }
            if (($signature['size'] ?? 0) > 1024) {
                return new JSONResponse(
                    ['error' => 'Signaturdatei zu gross — erwartet ~64 Bytes.'],
                    Http::STATUS_BAD_REQUEST
                );
            }

            $archivePath = $archive['tmp_name'] ?? '';
            $signaturePath = $signature['tmp_name'] ?? '';
            if (!is_string($archivePath) || !is_uploaded_file($archivePath)
                || !is_string($signaturePath) || !is_uploaded_file($signaturePath)
            ) {
                return new JSONResponse(
                    ['error' => 'Upload-Pfade ungueltig.'],
                    Http::STATUS_BAD_REQUEST
                );
            }

            $result = $this->updateService->performManualUpdate($archivePath, $signaturePath);
            return new JSONResponse($result);
        } catch (\RuntimeException $e) {
            $this->logger->warning('Manuelles Update abgelehnt', [
                'exception' => $e,
                'app' => 'mitgliederverwaltung',
            ]);
            return new JSONResponse(
                ['error' => $e->getMessage()],
                Http::STATUS_BAD_REQUEST
            );
        } catch (\Exception $e) {
            $this->logger->error('Manuelles Update fehlgeschlagen', [
                'exception' => $e,
                'app' => 'mitgliederverwaltung',
            ]);
            return new JSONResponse(
                ['error' => 'Manuelles Update fehlgeschlagen: ' . $e->getMessage()],
                Http::STATUS_INTERNAL_SERVER_ERROR
            );
        }
    }

    /** 25 MiB cap on manual archive uploads — plenty for this app. */
    private const MANUAL_UPLOAD_MAX_BYTES = 25 * 1024 * 1024;
}
