<?php
namespace EEPROM;

use RuntimeException;
use Index\Http\HttpFx;

require_once __DIR__ . '/../eeprom.php';

set_exception_handler(function(\Throwable $ex) {
    \Sentry\captureException($ex);

    ob_clean();
    http_response_code(500);

    if(PRM_DEBUG) {
        header('Content-Type: text/plain; charset=utf-8');
        echo (string)$ex;
    } else echo '500';
    exit;
});

function eepromOriginAllowed(string $origin): bool {
    global $cfg;

    $origin = mb_strtolower(parse_url($origin, PHP_URL_HOST));

    if($origin === $_SERVER['HTTP_HOST'])
        return true;

    $allowed = $cfg->getArray('cors:origins');
    if(empty($allowed))
        return true;

    return in_array($origin, $allowed);
}

function eepromUploadInfo(Uploads\UploadInfo $uploadInfo): array {
    global $eeprom;

    $uploadsCtx = $eeprom->getUploadsContext();

    return [
        'id' => $uploadInfo->getId(),
        'url' => $uploadsCtx->getFileUrlV1($uploadInfo),
        'urlf' => $uploadsCtx->getFileUrlV1($uploadInfo, true),
        'thumb' => $uploadsCtx->getThumbnailUrlV1($uploadInfo),
        'name' => $uploadInfo->getName(),
        'type' => $uploadInfo->getMediaTypeString(),
        'size' => $uploadInfo->getDataSize(),
        'user' => (int)$uploadInfo->getUserId(),
        'appl' => (int)$uploadInfo->getAppId(),
        'hash' => $uploadInfo->getHashString(),
        'created' => str_replace('+00:00', 'Z', $uploadInfo->getCreatedAt()->format(\DateTime::ATOM)),
        'accessed' => $uploadInfo->hasBeenAccessed() ? str_replace('+00:00', 'Z', $uploadInfo->getAccessedAt()->format(\DateTime::ATOM)) : null,
        'expires' => $uploadInfo->hasExpired() ? str_replace('+00:00', 'Z', $uploadInfo->getExpiredAt()->format(\DateTime::ATOM)) : null,

        // These can never be reached, and in situation where they technically could it's because of an outdated local record
        'deleted' => null,
        'dmca' => null,
    ];
}

$isApiDomain = $_SERVER['HTTP_HOST'] === $cfg->getString('domain:api');
$router = new HttpFx;

$router->use('/', function($response) {
    $response->setPoweredBy('EEPROM');
});

$router->use('/', function($response, $request) {
    $origin = $request->getHeaderLine('Origin');

    if(!empty($origin)) {
        if(!eepromOriginAllowed($origin))
            return 403;

        $response->setHeader('Access-Control-Allow-Origin', $origin);
        $response->setHeader('Vary', 'Origin');
    }
});

if($isApiDomain) {
    // this is illegal, don't do this
    $userInfo = null;

    $router->use('/', function($response, $request) {
        if($request->hasHeader('Origin'))
            $response->setHeader('Access-Control-Allow-Credentials', 'true');
    });

    $router->use('/', function($response, $request) {
        if($request->getMethod() === 'OPTIONS') {
            $response->setHeader('Access-Control-Allow-Headers', 'Authorization');
            $response->setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, POST, DELETE');
            return 204;
        }
    });

    $router->use('/', function($response, $request) use ($db, $cfg) {
        global $userInfo, $eeprom;

        $auth = $request->getHeaderLine('Authorization');
        if(empty($auth)) {
            $mszAuth = (string)$request->getCookie('msz_auth');
            if(!empty($mszAuth))
                $auth = 'Misuzu ' . $mszAuth;
        }

        if(!empty($auth)) {
            $authParts = explode(' ', $auth, 2);
            $authMethod = strval($authParts[0] ?? '');
            $authToken = strval($authParts[1] ?? '');

            $authClients = $cfg->getArray('auth:clients');

            foreach($authClients as $client) {
                $client = new $client;
                if($client->getName() !== $authMethod)
                    continue;
                $authUserId = $client->verifyToken($authToken);
                break;
            }

            if(isset($authUserId) && $authUserId > 0)
                $userInfo = $eeprom->getUsersContext()->getUser($authUserId);
        }
    });

    $router->get('/eeprom.js', function($response) {
        $response->accelRedirect('/js/eeprom-v1.0.js');
        $response->setContentType('application/javascript; charset=utf-8');
    });

    $router->get('/stats.json', function() use ($db) {
        $fileCount = 0;
        $userCount = 0;
        $totalSize = 0;
        $uniqueTypes = 0;

        $uploadStats = $db->query('SELECT COUNT(`upload_id`) AS `amount`, SUM(`upload_size`) AS `size`, COUNT(DISTINCT `upload_type`) AS `types` FROM `prm_uploads` WHERE `upload_deleted` IS NULL AND `upload_dmca` IS NULL');

        if($uploadStats->next()) {
            $fileCount = $uploadStats->getInteger(0);
            $totalSize = $uploadStats->getInteger(1);
            $uniqueTypes = $uploadStats->getInteger(2);
        }

        $userStats = $db->query('SELECT COUNT(`user_id`) AS `amount` FROM `prm_users` WHERE `user_restricted` IS NULL');

        if($userStats->next())
            $userCount = $userStats->getInteger(0);

        return [
            'size' => $totalSize,
            'files' => $fileCount,
            'types' => $uniqueTypes,
            'members' => $userCount,
        ];
    });

    $router->get('/', function($response) {
        $response->accelRedirect('/index.html');
        $response->setContentType('text/html; charset=utf-8');
    });

    $router->post('/uploads', function($response, $request) use ($db) {
        global $userInfo, $eeprom;

        if(!$request->isFormContent())
            return 400;

        $content = $request->getContent();

        try {
            $appInfo = $eeprom->getAppsContext()->getApp($content->getParam('src', FILTER_VALIDATE_INT));
        } catch(RuntimeException $ex) {
            return 404;
        }

        if($userInfo === null)
            return 401;

        if($userInfo->isRestricted())
            return 403;

        try {
            $file = $content->getUploadedFile('file');
        } catch(RuntimeException $ex) {
            return 400;
        }

        $maxFileSize = $appInfo->getDataSizeLimit();
        if($appInfo->allowSizeMultiplier())
            $maxFileSize *= $userInfo->getDataSizeMultiplier();

        $localFile = $file->getLocalFileName();
        $fileSize = filesize($localFile);

        if($file->getSize() !== $fileSize || $fileSize > $maxFileSize) {
            $response->setHeader('Access-Control-Expose-Headers', 'X-EEPROM-Max-Size');
            $response->setHeader('X-EEPROM-Max-Size', $maxFileSize);
            return 413;
        }

        $uploadsCtx = $eeprom->getUploadsContext();
        $uploadsData = $uploadsCtx->getUploadsData();

        $hash = hash_file('sha256', $localFile);

        // this is stupid: dmca status is stored as a file record rather than in a separate table requiring this hack ass garbage
        $uploadInfo = $uploadsData->getUpload(appInfo: $appInfo, userInfo: $userInfo, hashString: $hash)
            ?? $uploadsData->getUpload(hashString: $hash);

        if($uploadInfo !== null) {
            if($uploadInfo->isCopyrightTakedown())
                return 451;

            if($uploadInfo->getUserId() !== $userInfo->getId()
                || $uploadInfo->getAppId() !== $appInfo->getId())
                unset($uploadInfo);
        }

        if(empty($uploadInfo)) {
            $uploadInfo = $uploadsData->createUpload(
                $appInfo, $userInfo, $_SERVER['REMOTE_ADDR'],
                $file->getSuggestedFileName(), mime_content_type($localFile),
                $fileSize, $hash, $appInfo->getBumpAmount(), true
            );
            $filePath = $uploadsCtx->getFileDataPath($uploadInfo);
            $file->moveTo($filePath);
        } else {
            $filePath = $uploadsCtx->getFileDataPath($uploadInfo);
            if($uploadInfo->isDeleted())
                $uploadsData->restoreUpload($uploadInfo);

            $uploadsData->bumpUploadExpires($uploadInfo);
        }

        $response->setStatusCode(201);
        $response->setHeader('Content-Type', 'application/json; charset=utf-8');

        return eepromUploadInfo($uploadInfo);
    });

    $router->delete('/uploads/:fileid', function($response, $request, $fileId) use ($db) {
        global $userInfo, $eeprom;

        if($userInfo === null)
            return 401;

        $uploadsData = $eeprom->getUploadsContext()->getUploadsData();

        $uploadInfo = $uploadsData->getUpload(uploadId: $fileId);
        if($uploadInfo === null) {
            $response->setContent('File not found.');
            return 404;
        }

        if($uploadInfo->isCopyrightTakedown()) {
            $response->setContent('File is unavailable for copyright reasons.');
            return 451;
        }

        if($uploadInfo->isDeleted() || $uploadInfo->hasExpired()) {
            $response->setContent('File not found.');
            return 404;
        }

        if($userInfo->isRestricted() || $userInfo->getId() !== $uploadInfo->getUserId())
            return 403;

        $uploadsData->deleteUpload($uploadInfo);
        return 204;
    });

    $router->get('/uploads/:filename', function($response, $request, $fileName) use ($db) {
        global $eeprom;

        $pathInfo = pathinfo($fileName);
        $fileId = $pathInfo['filename'];
        $fileExt = $pathInfo['extension'] ?? '';
        $isThumbnail = $fileExt === 't';
        $isJson = $fileExt === 'json';

        if($fileExt !== '' && $fileExt !== 't' && $fileExt !== 'json')
            return 404;

        $uploadsCtx = $eeprom->getUploadsContext();
        $uploadsData = $uploadsCtx->getUploadsData();

        $uploadInfo = $uploadsData->getUpload(uploadId: $fileId);
        if($uploadInfo === null) {
            $response->setContent('File not found.');
            return 404;
        }

        if($uploadInfo->isCopyrightTakedown()) {
            $response->setContent('File is unavailable for copyright reasons.');
            return 451;
        }

        if($uploadInfo->isDeleted() || $uploadInfo->hasExpired()) {
            $response->setContent('File not found.');
            return 404;
        }

        if($isJson)
            return eepromUploadInfo($uploadInfo);

        $filePath = $uploadsCtx->getFileDataPath($uploadInfo);
        if(!is_file($filePath)) {
            $response->setContent('Data is missing.');
            return 404;
        }

        if(!$isThumbnail) {
            $uploadsData->bumpUploadAccess($uploadInfo);
            $uploadsData->bumpUploadExpires($uploadInfo);
        }

        $fileName = $uploadInfo->getName();
        $contentType = $uploadInfo->getMediaTypeString();

        if($contentType === 'application/octet-stream' || str_starts_with($contentType, 'text/'))
            $contentType = 'text/plain';

        if($isThumbnail) {
            if(!$uploadsCtx->supportsThumbnailing($uploadInfo))
                return 404;

            $contentType = 'image/jpeg';
            $accelRedirectPath = $uploadsCtx->getThumbnailDataRedirectPathOrCreate($uploadInfo);
            $fileName = pathinfo($fileName, PATHINFO_FILENAME) . '-thumb.jpg';
        } else {
            $accelRedirectPath = $uploadsCtx->getFileDataRedirectPath($uploadInfo);
        }

        $response->accelRedirect($accelRedirectPath);
        $response->setContentType($contentType);
        $response->setFileName(addslashes($fileName));
    });
} else {
    $router->use('/', function($response, $request) {
        if($request->getMethod() === 'OPTIONS') {
            $response->setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
            return 204;
        }
    });

    $router->get('/:filename', function($response, $request, $fileName) use ($db) {
        global $eeprom;

        $pathInfo = pathinfo($fileName);
        $fileId = $pathInfo['filename'];
        $fileExt = $pathInfo['extension'] ?? '';
        $isThumbnail = $fileExt === 't';

        if($fileExt !== '' && $fileExt !== 't')
            return 404;

        $uploadsCtx = $eeprom->getUploadsContext();
        $uploadsData = $uploadsCtx->getUploadsData();

        $uploadInfo = $uploadsData->getUpload(uploadId: $fileId);
        if($uploadInfo === null) {
            $response->setContent('File not found.');
            return 404;
        }

        if($uploadInfo->isCopyrightTakedown()) {
            $response->setContent('File is unavailable for copyright reasons.');
            return 451;
        }

        if($uploadInfo->isDeleted() || $uploadInfo->hasExpired()) {
            $response->setContent('File not found.');
            return 404;
        }

        $filePath = $uploadsCtx->getFileDataPath($uploadInfo);
        if(!is_file($filePath)) {
            $response->setContent('Data is missing.');
            return 404;
        }

        if(!$isThumbnail) {
            $uploadsData->bumpUploadAccess($uploadInfo);
            $uploadsData->bumpUploadExpires($uploadInfo);
        }

        $fileName = $uploadInfo->getName();
        $contentType = $uploadInfo->getMediaTypeString();

        if($contentType === 'application/octet-stream' || str_starts_with($contentType, 'text/'))
            $contentType = 'text/plain';

        if($isThumbnail) {
            if(!$uploadsCtx->supportsThumbnailing($uploadInfo))
                return 404;

            $contentType = 'image/jpeg';
            $accelRedirectPath = $uploadsCtx->getThumbnailDataRedirectPathOrCreate($uploadInfo);
            $fileName = pathinfo($fileName, PATHINFO_FILENAME) . '-thumb.jpg';
        } else {
            $accelRedirectPath = $uploadsCtx->getFileDataRedirectPath($uploadInfo);
        }

        $response->accelRedirect($accelRedirectPath);
        $response->setContentType($contentType);
        $response->setFileName(addslashes($fileName));
    });
}

$router->dispatch();