<?php

namespace app\controller\Api;

use app\service\AdminAuthService;
use app\service\AdminConsole\AdminDocService;
use app\service\AdminConsole\ArchiveAdminService;
use app\service\AdminConsole\MaintenanceScriptService;
use app\service\AdminConsole\OpenSearchAdminService;
use app\service\AdminUserRepository;
use InvalidArgumentException;
use JsonException;
use support\Request;
use support\Response;
use Throwable;

class AdminConsoleController
{
    public function archives(Request $request): Response
    {
        if ($guard = $this->guard($request)) {
            return $guard;
        }

        try {
            $data = (new ArchiveAdminService())->list(
                trim((string) $request->get('query', '')),
                (int) $request->get('page', 1),
                (int) $request->get('page_size', 20),
            );
        } catch (Throwable $exception) {
            return $this->error(500, 'Archive list lookup failed.', ['archives' => $exception->getMessage()]);
        }

        return $this->ok('Archive list loaded.', $data);
    }

    public function archive(Request $request, string $archiveUid): Response
    {
        if ($guard = $this->guard($request)) {
            return $guard;
        }

        try {
            $archive = (new ArchiveAdminService())->detail($archiveUid);
        } catch (Throwable $exception) {
            return $this->error(500, 'Archive lookup failed.', ['archive' => $exception->getMessage()]);
        }

        if ($archive === null) {
            return $this->error(404, 'Archive not found.', ['archive_uid' => $archiveUid], 404);
        }

        return $this->ok('Archive loaded.', $archive);
    }

    public function updateArchive(Request $request, string $archiveUid): Response
    {
        if ($guard = $this->guard($request)) {
            return $guard;
        }

        try {
            $archive = (new ArchiveAdminService())->update($archiveUid, $this->payload($request));
        } catch (JsonException $exception) {
            return $this->error(400, 'Invalid JSON body.', ['body' => $exception->getMessage()], 400);
        } catch (InvalidArgumentException $exception) {
            return $this->error(422, 'Archive update validation failed.', ['archive' => $exception->getMessage()], 422);
        } catch (Throwable $exception) {
            return $this->error(500, 'Archive update failed.', ['archive' => $exception->getMessage()]);
        }

        if ($archive === null) {
            return $this->error(404, 'Archive not found.', ['archive_uid' => $archiveUid], 404);
        }

        return $this->ok('Archive updated.', $archive);
    }

    public function deleteArchive(Request $request, string $archiveUid): Response
    {
        if ($guard = $this->guard($request)) {
            return $guard;
        }

        try {
            $deleted = (new ArchiveAdminService())->delete($archiveUid);
        } catch (Throwable $exception) {
            return $this->error(500, 'Archive delete failed.', ['archive' => $exception->getMessage()]);
        }

        if (!$deleted) {
            return $this->error(404, 'Archive not found.', ['archive_uid' => $archiveUid], 404);
        }

        return $this->ok('Archive deleted.', ['archive_uid' => $archiveUid]);
    }

    public function openSearchStatus(Request $request): Response
    {
        if ($guard = $this->guard($request)) {
            return $guard;
        }

        try {
            $status = (new OpenSearchAdminService())->status();
        } catch (Throwable $exception) {
            return $this->error(500, 'OpenSearch status lookup failed.', ['opensearch' => $exception->getMessage()]);
        }

        return $this->ok('OpenSearch status loaded.', $status);
    }

    public function openSearchDocuments(Request $request): Response
    {
        if ($guard = $this->guard($request)) {
            return $guard;
        }

        try {
            $documents = (new OpenSearchAdminService())->documents(
                trim((string) $request->get('query', '')),
                (int) $request->get('size', 20),
            );
        } catch (Throwable $exception) {
            return $this->error(500, 'OpenSearch document lookup failed.', ['opensearch' => $exception->getMessage()]);
        }

        return $this->ok('OpenSearch documents loaded.', $documents);
    }

    public function users(Request $request): Response
    {
        if ($guard = $this->guard($request)) {
            return $guard;
        }

        try {
            $users = (new AdminUserRepository())->listAll();
        } catch (Throwable $exception) {
            return $this->error(500, 'Admin users lookup failed.', ['users' => $exception->getMessage()]);
        }

        return $this->ok('Admin users loaded.', ['items' => array_map(fn (array $user): array => $this->sanitizeUser($user), $users)]);
    }

    public function createUser(Request $request): Response
    {
        if ($guard = $this->guard($request)) {
            return $guard;
        }

        try {
            $payload = $this->payload($request);
            $username = trim((string) ($payload['username'] ?? ''));
            $password = trim((string) ($payload['password'] ?? ''));
            $displayName = trim((string) ($payload['display_name'] ?? ''));

            if ($username === '' || $password === '') {
                throw new InvalidArgumentException('username and password are required.');
            }

            $repository = new AdminUserRepository();
            if ($repository->findAnyByUsername($username)) {
                throw new InvalidArgumentException('username already exists.');
            }

            $user = $repository->create($username, $password, $displayName !== '' ? $displayName : null);
        } catch (JsonException $exception) {
            return $this->error(400, 'Invalid JSON body.', ['body' => $exception->getMessage()], 400);
        } catch (InvalidArgumentException $exception) {
            return $this->error(422, 'Admin user creation validation failed.', ['user' => $exception->getMessage()], 422);
        } catch (Throwable $exception) {
            return $this->error(500, 'Admin user creation failed.', ['user' => $exception->getMessage()]);
        }

        return $this->ok('Admin user created.', ['user' => $this->sanitizeUser($user)]);
    }

    public function updateUser(Request $request, int $id): Response
    {
        if ($guard = $this->guard($request)) {
            return $guard;
        }

        try {
            $payload = $this->payload($request);
            $repository = new AdminUserRepository();
            if ($repository->findAnyById($id) === null) {
                return $this->error(404, 'Admin user not found.', ['id' => $id], 404);
            }

            $updates = [];
            if (array_key_exists('display_name', $payload)) {
                $updates['display_name'] = $payload['display_name'];
            }
            if (array_key_exists('password', $payload)) {
                $updates['password'] = $payload['password'];
            }
            if (array_key_exists('is_active', $payload)) {
                $updates['is_active'] = (bool) $payload['is_active'];
            }

            $user = $repository->updateUser($id, $updates);
        } catch (JsonException $exception) {
            return $this->error(400, 'Invalid JSON body.', ['body' => $exception->getMessage()], 400);
        } catch (Throwable $exception) {
            return $this->error(500, 'Admin user update failed.', ['user' => $exception->getMessage()]);
        }

        return $this->ok('Admin user updated.', ['user' => $this->sanitizeUser($user ?? [])]);
    }

    public function docs(Request $request): Response
    {
        if ($guard = $this->guard($request)) {
            return $guard;
        }

        try {
            $docs = (new AdminDocService())->list();
        } catch (Throwable $exception) {
            return $this->error(500, 'API docs lookup failed.', ['docs' => $exception->getMessage()]);
        }

        return $this->ok('API docs loaded.', ['items' => $docs]);
    }

    public function doc(Request $request, string $name): Response
    {
        if ($guard = $this->guard($request)) {
            return $guard;
        }

        try {
            $doc = (new AdminDocService())->read($name);
        } catch (Throwable $exception) {
            return $this->error(404, 'API doc not found.', ['doc' => $exception->getMessage()], 404);
        }

        return $this->ok('API doc loaded.', $doc);
    }

    public function scripts(Request $request): Response
    {
        if ($guard = $this->guard($request)) {
            return $guard;
        }

        return $this->ok('Maintenance scripts loaded.', ['items' => (new MaintenanceScriptService())->list()]);
    }

    public function script(Request $request, string $name): Response
    {
        if ($guard = $this->guard($request)) {
            return $guard;
        }

        try {
            $script = (new MaintenanceScriptService())->describe($name);
        } catch (Throwable $exception) {
            return $this->error(404, 'Maintenance script not found.', ['script' => $exception->getMessage()], 404);
        }

        return $this->ok('Maintenance script loaded.', $script);
    }

    public function runScript(Request $request): Response
    {
        if ($guard = $this->guard($request)) {
            return $guard;
        }

        try {
            $payload = $this->payload($request);
            $scriptName = trim((string) ($payload['script_name'] ?? ''));
            $args = $payload['args'] ?? [];

            if ($scriptName === '') {
                throw new InvalidArgumentException('script_name is required.');
            }
            if (!is_array($args)) {
                throw new InvalidArgumentException('args must be an array.');
            }

            $result = (new MaintenanceScriptService())->run($scriptName, $args);
        } catch (JsonException $exception) {
            return $this->error(400, 'Invalid JSON body.', ['body' => $exception->getMessage()], 400);
        } catch (InvalidArgumentException $exception) {
            return $this->error(422, 'Script execution validation failed.', ['script' => $exception->getMessage()], 422);
        } catch (Throwable $exception) {
            return $this->error(500, 'Script execution failed.', ['script' => $exception->getMessage()]);
        }

        return $this->ok('Maintenance script finished.', $result);
    }

    private function guard(Request $request): ?Response
    {
        return (new AdminAuthService())->current($request) === null
            ? $this->error(401, 'Admin session not found.', [], 401)
            : null;
    }

    /**
     * @throws JsonException
     */
    private function payload(Request $request): array
    {
        $rawBody = trim($request->rawBody());
        if ($rawBody === '') {
            return $request->post();
        }

        $payload = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
        return is_array($payload) ? $payload : [];
    }

    private function ok(string $message, array $data): Response
    {
        return $this->jsonResponse([
            'code' => 0,
            'message' => $message,
            'data' => $data,
        ], 200);
    }

    private function error(int $code, string $message, array $errors = [], int $status = 500): Response
    {
        return $this->jsonResponse([
            'code' => $code,
            'message' => $message,
            'errors' => $errors,
        ], $status);
    }

    private function sanitizeUser(array $user): array
    {
        unset($user['password_hash']);
        return $user;
    }

    private function jsonResponse(array $data, int $status): Response
    {
        return response(
            json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR),
            $status,
            ['Content-Type' => 'application/json']
        );
    }
}
