<?php
namespace App\Controllers;

use App\Core\Auth;
use App\Core\Csrf;
use App\Core\R2;
use App\Models\FileModel;
use App\Models\FolderModel;

class FilesController
{
    public function index(array $params): void
    {
        Auth::requireAuth();
        $folderId = isset($_GET['folder_id']) && $_GET['folder_id'] !== '' ? (int)$_GET['folder_id'] : null;

        $files = FileModel::allInFolder($folderId);
        $folders = FolderModel::allInParent($folderId);
        $base = CONFIG['APP_URL'] . CONFIG['BASE_PATH'];

        foreach ($files as &$f) {
            $f['shareUrl'] = $f['shared_enabled'] && $f['shared_token']
                ? $base . '/s/' . $f['shared_token']
                : null;
        }
        foreach ($folders as &$fo) {
            $fo['shareUrl'] = $fo['shared_enabled'] && $fo['shared_token']
                ? $base . '/s/' . $fo['shared_token']
                : null;
        }

        $breadcrumb = [];
        if ($folderId) {
            $breadcrumb = FolderModel::breadcrumb($folderId);
        }

        $this->json([
            'files'      => $files,
            'folders'    => $folders,
            'breadcrumb' => $breadcrumb,
            'folderId'   => $folderId,
        ]);
    }

    public function trash(array $params): void
    {
        Auth::requireAuth();
        $files = FileModel::allTrashed();
        $folders = FolderModel::allTrashed();
        $this->json(['files' => $files, 'folders' => $folders]);
    }

    public function trashCount(array $params): void
    {
        Auth::requireAuth();
        $fc = FileModel::trashedCount();
        $foc = FolderModel::trashedCount();
        $this->json(['count' => $fc + $foc]);
    }

    public function initUpload(array $params): void
    {
        Auth::requireAuth();
        Csrf::validate();

        $body = $this->body();
        $fileName  = trim($body['fileName'] ?? '');
        $mimeType  = trim($body['mimeType'] ?? 'application/octet-stream');
        $sizeBytes = (int)($body['sizeBytes'] ?? 0);

        if ($fileName === '') {
            $this->error('Nama file wajib diisi', 'VALIDATION', 400);
        }

        $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
        if (in_array($ext, CONFIG['BLOCKED_EXTENSIONS'], true)) {
            $this->error('Ekstensi file tidak diizinkan: .' . $ext, 'BLOCKED_EXT', 400);
        }

        $mimeAllowed = false;
        foreach (CONFIG['ALLOWED_MIME_PREFIXES'] as $prefix) {
            if (str_starts_with($mimeType, $prefix)) { $mimeAllowed = true; break; }
        }
        if (!$mimeAllowed) {
            $this->error('Tipe file tidak diizinkan: ' . $mimeType, 'BLOCKED_MIME', 400);
        }

        if ($sizeBytes > CONFIG['MAX_UPLOAD_BYTES']) {
            $maxMB = round(CONFIG['MAX_UPLOAD_BYTES'] / 1048576);
            $this->error("Ukuran file melebihi batas maksimal ({$maxMB} MB)", 'FILE_TOO_LARGE', 400);
        }

        $used = FileModel::totalUsedBytes();
        $quota = CONFIG['QUOTA_BYTES'] ?? 0;
        if ($quota > 0 && ($used + $sizeBytes) > $quota) {
            $this->error('Kuota penyimpanan tidak mencukupi', 'QUOTA_EXCEEDED', 400);
        }

        $uuid = bin2hex(random_bytes(16));
        $safeExt = $ext ? ".{$ext}" : '';
        $objectKey = "uploads/{$uuid}{$safeExt}";

        $r2 = new R2();
        $expireSeconds = 300;
        $uploadUrl = $r2->presignUrl('PUT', $objectKey, $expireSeconds, [], $mimeType);

        $this->json([
            'objectKey'   => $objectKey,
            'uploadUrl'   => $uploadUrl,
            'expiresIn'   => $expireSeconds,
            'contentType' => $mimeType,
        ]);
    }

    public function completeUpload(array $params): void
    {
        Auth::requireAuth();
        Csrf::validate();

        $body = $this->body();
        $objectKey    = trim($body['objectKey'] ?? '');
        $originalName = trim($body['originalName'] ?? '');
        $displayName  = trim($body['displayName'] ?? $originalName);
        $mimeType     = trim($body['mimeType'] ?? 'application/octet-stream');
        $folderId     = isset($body['folderId']) && $body['folderId'] !== null ? (int)$body['folderId'] : null;

        if ($objectKey === '' || $originalName === '') {
            $this->error('Data tidak lengkap', 'VALIDATION', 400);
        }

        $displayName = $this->sanitizeDisplayName($displayName);
        if ($displayName === '') $displayName = $originalName;

        $r2 = new R2();
        $head = $r2->signedRequest('HEAD', $objectKey);

        if ($head['httpCode'] !== 200) {
            $this->error('File tidak ditemukan di storage.', 'OBJECT_NOT_FOUND', 400);
        }

        $sizeBytes = (int)($head['headers']['content-length'] ?? 0);

        $id = FileModel::create([
            'object_key'    => $objectKey,
            'original_name' => $originalName,
            'display_name'  => $displayName,
            'size_bytes'    => $sizeBytes,
            'mime_type'     => $mimeType,
            'folder_id'     => $folderId,
        ]);

        $file = FileModel::findById($id);
        $this->json(['success' => true, 'file' => $file]);
    }

    public function rename(array $params): void
    {
        Auth::requireAuth();
        Csrf::validate();

        $id = (int)($params['id'] ?? 0);
        $file = FileModel::findById($id);
        if (!$file) $this->error('File tidak ditemukan', 'NOT_FOUND', 404);

        $body = $this->body();
        $displayName = trim($body['displayName'] ?? '');
        $displayName = $this->sanitizeDisplayName($displayName);

        if ($displayName === '') $this->error('Nama tidak boleh kosong', 'VALIDATION', 400);
        if (mb_strlen($displayName) > 255) $this->error('Nama terlalu panjang', 'VALIDATION', 400);

        FileModel::updateDisplayName($id, $displayName);
        $file = FileModel::findById($id);
        $this->json(['success' => true, 'file' => $file]);
    }

    public function delete(array $params): void
    {
        Auth::requireAuth();
        Csrf::validate();

        $id = (int)($params['id'] ?? 0);
        $file = FileModel::findById($id);
        if (!$file) $this->error('File tidak ditemukan', 'NOT_FOUND', 404);

        // Check if permanent delete (from trash) or soft delete
        $permanent = !empty($_GET['permanent']);

        if ($permanent || $file['trashed_at']) {
            // Permanent delete
            $r2 = new R2();
            $r2->signedRequest('DELETE', $file['object_key']);
            FileModel::delete($id);
        } else {
            // Soft delete: move to trash
            FileModel::moveToTrash($id);
        }

        $this->json(['success' => true]);
    }

    public function restore(array $params): void
    {
        Auth::requireAuth();
        Csrf::validate();

        $id = (int)($params['id'] ?? 0);
        $file = FileModel::findById($id);
        if (!$file) $this->error('File tidak ditemukan', 'NOT_FOUND', 404);

        FileModel::restoreFromTrash($id);
        $this->json(['success' => true]);
    }

    public function emptyTrash(array $params): void
    {
        Auth::requireAuth();
        Csrf::validate();

        $r2 = new R2();

        // Delete all trashed files permanently
        $files = FileModel::allTrashed();
        foreach ($files as $f) {
            $r2->signedRequest('DELETE', $f['object_key']);
            FileModel::delete($f['id']);
        }

        // Delete all trashed folders
        $folders = FolderModel::allTrashed();
        foreach ($folders as $fo) {
            FolderModel::delete($fo['id']);
        }

        $this->json(['success' => true]);
    }

    public function share(array $params): void
    {
        Auth::requireAuth();
        Csrf::validate();

        $id = (int)($params['id'] ?? 0);
        $file = FileModel::findById($id);
        if (!$file) $this->error('File tidak ditemukan', 'NOT_FOUND', 404);

        $body = $this->body();
        $enabled = !empty($body['enabled']);

        $token = $file['shared_token'];
        if ($enabled && !$token) {
            $token = $this->generateShareToken();
        }

        FileModel::updateShare($id, $enabled, $enabled ? $token : $file['shared_token']);

        $shareUrl = null;
        if ($enabled && $token) {
            $base = CONFIG['APP_URL'] . CONFIG['BASE_PATH'];
            $shareUrl = $base . '/s/' . $token;
        }

        $this->json(['success' => true, 'enabled' => $enabled, 'shareUrl' => $shareUrl, 'token' => $enabled ? $token : null]);
    }

    public function downloadUrl(array $params): void
    {
        Auth::requireAuth();
        $id = (int)($params['id'] ?? 0);
        $file = FileModel::findById($id);
        if (!$file) $this->error('File tidak ditemukan', 'NOT_FOUND', 404);

        $r2 = new R2();
        $disposition = 'attachment; filename="' . addcslashes($file['display_name'], '"\\') . '"';
        $url = $r2->presignUrl('GET', $file['object_key'], 300, ['response-content-disposition' => $disposition]);
        $this->json(['url' => $url]);
    }

    public function previewUrl(array $params): void
    {
        Auth::requireAuth();
        $id = (int)($params['id'] ?? 0);
        $file = FileModel::findById($id);
        if (!$file) $this->error('File tidak ditemukan', 'NOT_FOUND', 404);

        $r2 = new R2();
        $disposition = 'inline; filename="' . addcslashes($file['display_name'], '"\\') . '"';
        $url = $r2->presignUrl('GET', $file['object_key'], 300, [
            'response-content-disposition' => $disposition,
            'response-content-type' => $file['mime_type'],
        ]);
        $this->json(['url' => $url, 'mimeType' => $file['mime_type']]);
    }

    public function usage(array $params): void
    {
        Auth::requireAuth();
        $used = FileModel::totalUsedBytes();
        $quota = CONFIG['QUOTA_BYTES'] ?? 0;
        $percent = $quota > 0 ? round(($used / $quota) * 100, 1) : 0;

        $this->json([
            'usedBytes'  => $used,
            'usedHuman'  => $this->humanSize($used),
            'quotaBytes' => $quota,
            'quotaHuman' => $this->humanSize($quota),
            'percent'    => $percent,
        ]);
    }

    // --- Folder endpoints ---
    public function createFolder(array $params): void
    {
        Auth::requireAuth();
        Csrf::validate();

        $body = $this->body();
        $name = trim($body['name'] ?? '');
        $parentId = isset($body['parentId']) && $body['parentId'] !== null ? (int)$body['parentId'] : null;

        $name = $this->sanitizeDisplayName($name);
        if ($name === '') $this->error('Nama folder wajib diisi', 'VALIDATION', 400);
        if (mb_strlen($name) > 255) $this->error('Nama folder terlalu panjang', 'VALIDATION', 400);

        $id = FolderModel::create($name, $parentId);
        $folder = FolderModel::findById($id);
        $this->json(['success' => true, 'folder' => $folder]);
    }

    public function renameFolder(array $params): void
    {
        Auth::requireAuth();
        Csrf::validate();

        $id = (int)($params['id'] ?? 0);
        $folder = FolderModel::findById($id);
        if (!$folder) $this->error('Folder tidak ditemukan', 'NOT_FOUND', 404);

        $body = $this->body();
        $name = trim($body['name'] ?? '');
        $name = $this->sanitizeDisplayName($name);

        if ($name === '') $this->error('Nama tidak boleh kosong', 'VALIDATION', 400);
        if (mb_strlen($name) > 255) $this->error('Nama terlalu panjang', 'VALIDATION', 400);

        FolderModel::rename($id, $name);
        $folder = FolderModel::findById($id);
        $this->json(['success' => true, 'folder' => $folder]);
    }

    public function deleteFolder(array $params): void
    {
        Auth::requireAuth();
        Csrf::validate();

        $id = (int)($params['id'] ?? 0);
        $folder = FolderModel::findById($id);
        if (!$folder) $this->error('Folder tidak ditemukan', 'NOT_FOUND', 404);

        $permanent = !empty($_GET['permanent']);

        if ($permanent || $folder['trashed_at']) {
            // Permanently delete folder + its files from R2
            $r2 = new R2();
            $files = FolderModel::allFilesRecursive($id);
            foreach ($files as $f) {
                $r2->signedRequest('DELETE', $f['object_key']);
                FileModel::delete($f['id']);
            }
            FolderModel::delete($id);
        } else {
            FolderModel::moveToTrash($id);
        }

        $this->json(['success' => true]);
    }

    public function restoreFolder(array $params): void
    {
        Auth::requireAuth();
        Csrf::validate();

        $id = (int)($params['id'] ?? 0);
        $folder = FolderModel::findById($id);
        if (!$folder) $this->error('Folder tidak ditemukan', 'NOT_FOUND', 404);

        FolderModel::restoreFromTrash($id);
        $this->json(['success' => true]);
    }

    public function shareFolder(array $params): void
    {
        Auth::requireAuth();
        Csrf::validate();

        $id = (int)($params['id'] ?? 0);
        $folder = FolderModel::findById($id);
        if (!$folder) $this->error('Folder tidak ditemukan', 'NOT_FOUND', 404);

        $body = $this->body();
        $enabled = !empty($body['enabled']);

        $token = $folder['shared_token'];
        if ($enabled && !$token) {
            $token = $this->generateShareToken();
        }

        FolderModel::updateShare($id, $enabled, $enabled ? $token : $folder['shared_token']);

        $shareUrl = null;
        if ($enabled && $token) {
            $base = CONFIG['APP_URL'] . CONFIG['BASE_PATH'];
            $shareUrl = $base . '/s/' . $token;
        }

        $this->json(['success' => true, 'enabled' => $enabled, 'shareUrl' => $shareUrl, 'token' => $enabled ? $token : null]);
    }

    // --- Move endpoints ---
    public function moveFile(array $params): void
    {
        Auth::requireAuth();
        Csrf::validate();

        $id = (int)($params['id'] ?? 0);
        $file = FileModel::findById($id);
        if (!$file) $this->error('File tidak ditemukan', 'NOT_FOUND', 404);

        $body = $this->body();
        $folderId = isset($body['folderId']) && $body['folderId'] !== null ? (int)$body['folderId'] : null;

        // Validate target folder exists
        if ($folderId !== null) {
            $target = FolderModel::findById($folderId);
            if (!$target) $this->error('Folder tujuan tidak ditemukan', 'NOT_FOUND', 404);
        }

        FileModel::moveToFolder($id, $folderId);
        $this->json(['success' => true]);
    }

    public function moveFolderAction(array $params): void
    {
        Auth::requireAuth();
        Csrf::validate();

        $id = (int)($params['id'] ?? 0);
        $folder = FolderModel::findById($id);
        if (!$folder) $this->error('Folder tidak ditemukan', 'NOT_FOUND', 404);

        $body = $this->body();
        $parentId = isset($body['parentId']) && $body['parentId'] !== null ? (int)$body['parentId'] : null;

        // Cannot move folder into itself
        if ($parentId === $id) {
            $this->error('Tidak dapat memindahkan folder ke dalam dirinya sendiri', 'INVALID_MOVE', 400);
        }

        // Cannot move folder into its own descendant
        if ($parentId !== null) {
            $target = FolderModel::findById($parentId);
            if (!$target) $this->error('Folder tujuan tidak ditemukan', 'NOT_FOUND', 404);

            // Check if target is a descendant of the folder being moved
            $check = $target;
            while ($check && $check['parent_id'] !== null) {
                if ((int)$check['parent_id'] === $id) {
                    $this->error('Tidak dapat memindahkan folder ke dalam sub-foldernya', 'INVALID_MOVE', 400);
                }
                $check = FolderModel::findById((int)$check['parent_id']);
            }
        }

        FolderModel::moveToParent($id, $parentId);
        $this->json(['success' => true]);
    }

    // --- Helpers ---
    private function generateShareToken(): string
    {
        return rtrim(strtr(base64_encode(random_bytes(18)), '+/', '-_'), '=');
    }

    private function sanitizeDisplayName(string $name): string
    {
        $name = preg_replace('/[<>:"\/\\\\|?*\x00-\x1f]/', '', $name);
        return trim($name);
    }

    private function humanSize(int $bytes): string
    {
        if ($bytes === 0) return '0 B';
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $i = 0; $val = (float)$bytes;
        while ($val >= 1024 && $i < count($units) - 1) { $val /= 1024; $i++; }
        return round($val, 1) . ' ' . $units[$i];
    }

    private function json(mixed $data, int $code = 0): void
    {
        if ($code) http_response_code($code);
        header('Content-Type: application/json');
        echo json_encode($data, JSON_UNESCAPED_UNICODE);
        exit;
    }

    private function error(string $message, string $code, int $httpCode): void
    {
        http_response_code($httpCode);
        $this->json(['error' => ['message' => $message, 'code' => $code]]);
    }

    private function body(): array
    {
        return json_decode(file_get_contents('php://input'), true) ?? [];
    }
}
