<?php

namespace FileRise\Http\Controllers;

use FileRise\Support\ACL;
use FileRise\Storage\SourceContext;
use FileRise\Domain\UploadModel;
use FileRise\Http\Controllers\PortalController;

// src/controllers/UploadController.php

require_once dirname(__DIR__, 4) . '/config/config.php';
require_once PROJECT_ROOT . '/src/lib/ACL.php';
require_once PROJECT_ROOT . '/src/lib/SourceContext.php';

class UploadController
{
    public function handleUpload(): void
    {
        header('Content-Type: application/json');

        $method         = $_SERVER['REQUEST_METHOD'] ?? 'GET';
        $requestParams  = ($method === 'GET') ? $_GET : array_merge($_GET, $_POST);

        // Detect Resumable.js chunk "test" requests (testChunks=true, default GET)
        $isResumableTest =
            ($method === 'GET'
             && isset($requestParams['resumableChunkNumber'])
             && isset($requestParams['resumableIdentifier']));

        // ---- 1) CSRF (skip for resumable GET tests – Resumable only cares about HTTP status) ----
        if (!$isResumableTest) {
            $headersArr = array_change_key_case(getallheaders() ?: [], CASE_LOWER);
            $received   = '';

            if (!empty($headersArr['x-csrf-token'])) {
                $received = trim($headersArr['x-csrf-token']);
            } elseif (!empty($requestParams['csrf_token'])) {
                $received = trim((string)$requestParams['csrf_token']);
            } elseif (!empty($requestParams['upload_token'])) {
                // legacy alias
                $received = trim((string)$requestParams['upload_token']);
            }

            if (!isset($_SESSION['csrf_token']) || $received !== $_SESSION['csrf_token']) {
                // Soft-fail so client can retry with refreshed token
                $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
                http_response_code(200);
                echo json_encode([
                    'csrf_expired' => true,
                    'csrf_token'   => $_SESSION['csrf_token'],
                ]);
                return;
            }
        }

        // ---- 2) Auth + account-level flags ----
        if (empty($_SESSION['authenticated'])) {
            http_response_code(401);
            echo json_encode(['error' => 'Unauthorized']);
            return;
        }

        $username  = (string)($_SESSION['username'] ?? '');
        $userPerms = loadUserPermissions($username) ?: [];
        $isAdmin   = ACL::isAdmin($userPerms);

        // Admins should never be blocked by account-level "disableUpload"
        if (!$isAdmin && !empty($userPerms['disableUpload'])) {
            http_response_code(403);
            echo json_encode(['error' => 'Upload disabled for this user.']);
            return;
        }

        $sourceId = trim((string)($requestParams['sourceId'] ?? ($_GET['sourceId'] ?? '')));
        if ($sourceId !== '' && class_exists('SourceContext') && SourceContext::sourcesEnabled()) {
            if (!preg_match('/^[A-Za-z0-9_-]{1,64}$/', $sourceId)) {
                http_response_code(400);
                echo json_encode(['error' => 'Invalid source id.']);
                return;
            }
            $info = SourceContext::getSourceById($sourceId);
            if (!$info) {
                http_response_code(400);
                echo json_encode(['error' => 'Invalid source.']);
                return;
            }
            if (empty($info['enabled']) && !$isAdmin) {
                http_response_code(403);
                echo json_encode(['error' => 'Source is disabled.']);
                return;
            }
            SourceContext::setActiveId($sourceId, false, $isAdmin);
        }

        if (class_exists('SourceContext') && SourceContext::isReadOnly()) {
            http_response_code(403);
            echo json_encode(['error' => 'Source is read-only.']);
            return;
        }

        $sourceLabel = strtolower((string)($requestParams['source'] ?? ($_GET['source'] ?? '')));
        $portalSlug  = trim((string)($requestParams['portal'] ?? ($_GET['portal'] ?? '')));
        $isPortalUpload = ($sourceLabel === 'portal' && $portalSlug !== '');
        if ($sourceLabel === 'portal' && $portalSlug === '') {
            http_response_code(400);
            echo json_encode(['error' => 'Missing portal slug.']);
            return;
        }

        // ---- 3) Folder-level WRITE permission (ACL) ----
        // Prefer the unified param array, fall back to GET only if needed.
        $folderParam = isset($requestParams['folder'])
            ? (string)$requestParams['folder']
            : (isset($_GET['folder']) ? (string)$_GET['folder'] : 'root');

        // Decode %xx (e.g., "test%20folder") then normalize
        $folderParam  = rawurldecode($folderParam);
        $targetFolder = ACL::normalizeFolder($folderParam);

        if ($isPortalUpload) {
            try {
                $portal = PortalController::getPortalBySlug($portalSlug);
            } catch (\Throwable $e) {
                http_response_code(403);
                echo json_encode(['error' => $e->getMessage() ?: 'Portal upload not allowed.']);
                return;
            }

            if (empty($portal['canUpload'])) {
                http_response_code(403);
                echo json_encode(['error' => 'Uploads are disabled for this portal.']);
                return;
            }

            $portalFolder = ACL::normalizeFolder((string)($portal['folder'] ?? 'root'));
            if ($portalFolder === '') {
                $portalFolder = 'root';
            }
            $allowSubfolders = !empty($portal['allowSubfolders']);

            $reqSourceId = trim((string)($requestParams['sourceId'] ?? ($_GET['sourceId'] ?? '')));
            $portalSourceId = (string)($portal['sourceId'] ?? '');
            if ($portalSourceId !== '' && $portalSourceId !== $reqSourceId) {
                http_response_code(403);
                echo json_encode(['error' => 'Portal source mismatch.']);
                return;
            }

            if (!$allowSubfolders) {
                $relativePath = (string)($requestParams['relativePath'] ?? ($requestParams['resumableRelativePath'] ?? ''));
                $relativePath = str_replace('\\', '/', trim($relativePath));
                $relativePath = trim($relativePath, '/');
                $hasSubfolder = ($relativePath !== '' && strpos($relativePath, '/') !== false);
                if ($hasSubfolder) {
                    http_response_code(403);
                    echo json_encode(['error' => 'Subfolder uploads are not allowed for this portal.']);
                    return;
                }
            }

            $withinPortal = false;
            if ($portalFolder === 'root') {
                $withinPortal = $allowSubfolders ? true : ($targetFolder === 'root');
            } else {
                if ($targetFolder === $portalFolder) {
                    $withinPortal = true;
                } elseif ($allowSubfolders) {
                    $withinPortal = (strpos($targetFolder . '/', $portalFolder . '/') === 0);
                }
            }

            if (!$withinPortal) {
                http_response_code(403);
                echo json_encode(['error' => 'Uploads must stay within the portal folder.']);
                return;
            }

            if (!$allowSubfolders) {
                $targetFolder = $portalFolder;
            }
        }

        // Admins bypass folder canWrite checks
        if (!$isAdmin && !ACL::canUpload($username, $userPerms, $targetFolder)) {
            http_response_code(403);
            echo json_encode([
                'error' => 'Forbidden: no write access to folder "' . $targetFolder . '".',
            ]);
            return;
        }

        // ---- 4) Delegate to model (force the sanitized folder) ----
        $requestParams['folder'] = $targetFolder;
        // Keep legacy behavior for anything still reading $_POST directly
        $_POST['folder'] = $targetFolder;

        $result = UploadModel::handleUpload($requestParams, $_FILES);

        // ---- 5) Special handling for Resumable.js GET tests ----
        // Resumable only inspects HTTP status:
        //   200 => chunk exists (skip)
        //   404/other => chunk missing (upload)
        if ($isResumableTest && isset($result['status'])) {
            if ($result['status'] === 'found') {
                http_response_code(200);
            } else {
                http_response_code(202); // 202 Accepted = chunk not found
            }
             echo json_encode($result);
            return;
        }

        // ---- 6) Normal response handling ----
        if (isset($result['error'])) {
            http_response_code(400);
            echo json_encode($result);
            return;
        }

        if (isset($result['status'])) {
            echo json_encode($result);
            return;
        }

        echo json_encode([
            'success'     => $result['success'] ?? 'File uploaded successfully',
            'newFilename' => $result['newFilename'] ?? null,
        ]);
    }

    public function removeChunks(): void
    {
        header('Content-Type: application/json');

        $receivedToken = isset($_POST['csrf_token']) ? trim((string)$_POST['csrf_token']) : '';
        if ($receivedToken !== ($_SESSION['csrf_token'] ?? '')) {
            http_response_code(403);
            echo json_encode(['error' => 'Invalid CSRF token']);
            return;
        }

        if (empty($_SESSION['authenticated'])) {
            http_response_code(401);
            echo json_encode(['error' => 'Unauthorized']);
            return;
        }

        $username  = (string)($_SESSION['username'] ?? '');
        $userPerms = loadUserPermissions($username) ?: [];
        $isAdmin   = ACL::isAdmin($userPerms);

        if (!$isAdmin && !empty($userPerms['disableUpload'])) {
            http_response_code(403);
            echo json_encode(['error' => 'Upload disabled for this user.']);
            return;
        }

        $sourceId = trim((string)($_POST['sourceId'] ?? ''));
        if ($sourceId !== '' && class_exists('SourceContext') && SourceContext::sourcesEnabled()) {
            if (!preg_match('/^[A-Za-z0-9_-]{1,64}$/', $sourceId)) {
                http_response_code(400);
                echo json_encode(['error' => 'Invalid source id.']);
                return;
            }
            $info = SourceContext::getSourceById($sourceId);
            if (!$info) {
                http_response_code(400);
                echo json_encode(['error' => 'Invalid source.']);
                return;
            }
            SourceContext::setActiveId($sourceId, false, true);
        }

        if (class_exists('SourceContext') && SourceContext::isReadOnly()) {
            http_response_code(403);
            echo json_encode(['error' => 'Source is read-only.']);
            return;
        }

        $targetFolderRaw = isset($_POST['targetFolder']) ? (string)$_POST['targetFolder'] : 'root';
        $targetFolder = ACL::normalizeFolder(rawurldecode($targetFolderRaw));
        if (!$isAdmin && !ACL::canUpload($username, $userPerms, $targetFolder)) {
            http_response_code(403);
            echo json_encode([
                'error' => 'Forbidden: no write access to folder "' . $targetFolder . '".',
            ]);
            return;
        }

        if (!isset($_POST['folder'])) {
            http_response_code(400);
            echo json_encode(['error' => 'No folder specified']);
            return;
        }

        $folder = rawurldecode((string)$_POST['folder']);

        echo json_encode(UploadModel::removeChunks($folder));
    }

    public function checkExisting(): void
    {
        header('Content-Type: application/json');

        $raw = file_get_contents('php://input') ?: '';
        $payload = json_decode($raw, true);
        if (!is_array($payload)) {
            http_response_code(400);
            echo json_encode(['error' => 'Invalid JSON payload.']);
            return;
        }

        $headersArr = array_change_key_case(getallheaders() ?: [], CASE_LOWER);
        $received = '';
        if (!empty($headersArr['x-csrf-token'])) {
            $received = trim($headersArr['x-csrf-token']);
        } elseif (!empty($payload['csrf_token'])) {
            $received = trim((string)$payload['csrf_token']);
        } elseif (!empty($payload['upload_token'])) {
            $received = trim((string)$payload['upload_token']);
        }

        if (!isset($_SESSION['csrf_token']) || $received !== $_SESSION['csrf_token']) {
            $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
            http_response_code(200);
            echo json_encode([
                'csrf_expired' => true,
                'csrf_token'   => $_SESSION['csrf_token'],
            ]);
            return;
        }

        if (empty($_SESSION['authenticated'])) {
            http_response_code(401);
            echo json_encode(['error' => 'Unauthorized']);
            return;
        }

        $username  = (string)($_SESSION['username'] ?? '');
        $userPerms = loadUserPermissions($username) ?: [];
        $isAdmin   = ACL::isAdmin($userPerms);

        if (!$isAdmin && !empty($userPerms['disableUpload'])) {
            http_response_code(403);
            echo json_encode(['error' => 'Upload disabled for this user.']);
            return;
        }

        $sourceId = trim((string)($payload['sourceId'] ?? ''));
        if ($sourceId !== '' && class_exists('SourceContext') && SourceContext::sourcesEnabled()) {
            if (!preg_match('/^[A-Za-z0-9_-]{1,64}$/', $sourceId)) {
                http_response_code(400);
                echo json_encode(['error' => 'Invalid source id.']);
                return;
            }
            $info = SourceContext::getSourceById($sourceId);
            if (!$info) {
                http_response_code(400);
                echo json_encode(['error' => 'Invalid source.']);
                return;
            }
            if (empty($info['enabled']) && !$isAdmin) {
                http_response_code(403);
                echo json_encode(['error' => 'Source is disabled.']);
                return;
            }
            SourceContext::setActiveId($sourceId, false, $isAdmin);
        }

        if (class_exists('SourceContext') && SourceContext::isReadOnly()) {
            http_response_code(403);
            echo json_encode(['error' => 'Source is read-only.']);
            return;
        }

        $folderParam  = isset($payload['folder']) ? (string)$payload['folder'] : 'root';
        $folderParam  = rawurldecode($folderParam);
        $targetFolder = ACL::normalizeFolder($folderParam);

        if (!$isAdmin && !ACL::canUpload($username, $userPerms, $targetFolder)) {
            http_response_code(403);
            echo json_encode([
                'error' => 'Forbidden: no write access to folder "' . $targetFolder . '".',
            ]);
            return;
        }

        $files = $payload['files'] ?? null;
        if (!is_array($files)) {
            http_response_code(400);
            echo json_encode(['error' => 'Invalid files list.']);
            return;
        }

        echo json_encode(UploadModel::checkExisting($targetFolder, $files));
    }
}
