<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Requests\StoreBastionAccountRequest;
use App\Http\Requests\UpdateBastionAccountRequest;
use App\Models\BastionAccount;
use hg\apidoc\annotation as Apidoc;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

#[Apidoc\Title('堡垒机授权账号管理')]
class BastionAccountController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth:api');
        $this->middleware('permission:platform.accounts.view,api')->only(['index', 'show']);
        $this->middleware('permission:platform.accounts.manage,api')->only(['store', 'update', 'destroy', 'refreshToken', 'refreshTokenStatus']);
    }

    #[Apidoc\Title('账号列表'), Apidoc\Method('GET'), Apidoc\Url('/accounts')]
    public function index(): JsonResponse
    {
        return response()->json(['code' => 0, 'message' => 'ok', 'data' => BastionAccount::query()->latest()->paginate(20)]);
    }

    #[Apidoc\Title('创建账号'), Apidoc\Method('POST'), Apidoc\Url('/accounts')]
    public function store(StoreBastionAccountRequest $request): JsonResponse
    {
        $account = BastionAccount::query()->create($request->validated());
        $this->auditLog($request, 'account_create', ['bastion_account_id' => $account->id]);

        return response()->json(['code' => 0, 'message' => 'ok', 'data' => $account], 201);
    }

    #[Apidoc\Title('账号详情'), Apidoc\Method('GET'), Apidoc\Url('/accounts/{id}')]
    public function show(int $id): JsonResponse
    {
        return response()->json(['code' => 0, 'message' => 'ok', 'data' => BastionAccount::query()->findOrFail($id)]);
    }

    #[Apidoc\Title('更新账号'), Apidoc\Method('PUT'), Apidoc\Url('/accounts/{id}')]
    public function update(UpdateBastionAccountRequest $request, int $id): JsonResponse
    {
        $account = BastionAccount::query()->findOrFail($id);
        $account->update($request->validated());
        $this->auditLog($request, 'account_update', ['bastion_account_id' => $account->id]);

        return response()->json(['code' => 0, 'message' => 'ok', 'data' => $account]);
    }

    #[Apidoc\Title('删除账号'), Apidoc\Method('DELETE'), Apidoc\Url('/accounts/{id}')]
    public function destroy(Request $request, int $id): JsonResponse
    {
        $account = BastionAccount::query()->findOrFail($id);
        $this->auditLog($request, 'account_delete', ['bastion_account_id' => $account->id]);
        $account->delete();

        return response()->json(['code' => 0, 'message' => 'ok', 'data' => null]);
    }

    #[Apidoc\Title('刷新账号token'), Apidoc\Method('POST'), Apidoc\Url('/accounts/{id}/refresh-token')]
    public function refreshToken(Request $request, int $id): JsonResponse
    {
        $account = BastionAccount::query()->findOrFail($id);
        $baseUrl = (string) config('services.bastion_token.base_url');

        if ($baseUrl === '') {
            return response()->json([
                'code' => 500,
                'message' => '未配置堡垒机 Token 服务地址，请先在 .env 中配置 BASTION_TOKEN_API_BASE_URL',
                'data' => null,
            ], 500);
        }

        $submitEndpoint = (string) config('services.bastion_token.submit_endpoint', '/bastion_token');
        $timeout = (int) config('services.bastion_token.timeout', 30);
        $taskTtlSeconds = (int) config('services.bastion_token.task_ttl_seconds', 1800);

        try {
            $submitResponse = Http::baseUrl($baseUrl)
                ->acceptJson()
                ->timeout($timeout)
                ->retry(2, 300, throw: false)
                ->post($submitEndpoint, [
                    'username' => $account->username,
                    'password' => $account->password,
                ]);

            if (! $submitResponse->successful()) {
                return response()->json([
                    'code' => 502,
                    'message' => '提交 Token 刷新任务失败',
                    'data' => ['response' => $submitResponse->json()],
                ], 502);
            }

            $taskId = (string) data_get($submitResponse->json(), 'task_id', '');

            if ($taskId === '') {
                return response()->json([
                    'code' => 502,
                    'message' => 'Token 服务返回任务ID为空',
                    'data' => ['response' => $submitResponse->json()],
                ], 502);
            }
        } catch (ConnectionException|RequestException $exception) {
            return response()->json([
                'code' => 502,
                'message' => '调用 Token 服务失败：'.$exception->getMessage(),
                'data' => null,
            ], 502);
        }

        $cacheKey = $this->tokenTaskCacheKey($taskId);
        Cache::put($cacheKey, [
            'task_id' => $taskId,
            'account_id' => $account->id,
            'finished' => false,
        ], now()->addSeconds(max(60, $taskTtlSeconds)));

        $this->auditLog($request, 'account_refresh_token_submit', ['bastion_account_id' => $account->id, 'task_id' => $taskId]);

        return response()->json([
            'code' => 0,
            'message' => 'Token 刷新任务已提交',
            'data' => [
                'task_id' => $taskId,
                'status' => 'pending',
            ],
        ]);
    }

    #[Apidoc\Title('查询刷新账号token状态'), Apidoc\Method('GET'), Apidoc\Url('/accounts/{id}/refresh-token/{taskId}')]
    public function refreshTokenStatus(Request $request, int $id, string $taskId): JsonResponse
    {
        $account = BastionAccount::query()->findOrFail($id);
        $baseUrl = (string) config('services.bastion_token.base_url');

        if ($baseUrl === '') {
            return response()->json([
                'code' => 500,
                'message' => '未配置堡垒机 Token 服务地址，请先在 .env 中配置 BASTION_TOKEN_API_BASE_URL',
                'data' => null,
            ], 500);
        }

        $cacheKey = $this->tokenTaskCacheKey($taskId);
        $taskMeta = Cache::get($cacheKey);

        if (! is_array($taskMeta) || (int) ($taskMeta['account_id'] ?? 0) !== $account->id) {
            return response()->json([
                'code' => 404,
                'message' => '任务不存在或已过期',
                'data' => ['task_id' => $taskId],
            ], 404);
        }

        $statusEndpoint = (string) config('services.bastion_token.status_endpoint', '/bastion_token/{task_id}');
        $timeout = (int) config('services.bastion_token.timeout', 30);
        $statusUrl = str_replace('{task_id}', $taskId, $statusEndpoint);

        try {
            $statusResponse = Http::baseUrl($baseUrl)
                ->acceptJson()
                ->timeout($timeout)
                ->retry(2, 300, throw: false)
                ->get($statusUrl);
        } catch (ConnectionException|RequestException $exception) {
            return response()->json([
                'code' => 502,
                'message' => '查询 Token 任务状态失败：'.$exception->getMessage(),
                'data' => ['task_id' => $taskId],
            ], 502);
        }

        if (! $statusResponse->successful()) {
            return response()->json([
                'code' => 502,
                'message' => '查询 Token 任务状态失败',
                'data' => ['task_id' => $taskId, 'response' => $statusResponse->json()],
            ], 502);
        }

        $taskResult = $statusResponse->json();
        $status = Str::lower((string) data_get($taskResult, 'status', 'pending'));

        if ($status !== 'success') {
            if ($status === 'error') {
                return response()->json([
                    'code' => 502,
                    'message' => 'Token 刷新失败：'.((string) data_get($taskResult, 'message', '未知错误')),
                    'data' => ['task_id' => $taskId, 'status' => $status, 'result' => $taskResult],
                ], 502);
            }

            return response()->json([
                'code' => 0,
                'message' => 'Token 刷新任务执行中',
                'data' => ['task_id' => $taskId, 'status' => 'pending'],
            ]);
        }

        if (! (bool) ($taskMeta['finished'] ?? false)) {
            $usmAuthentication = (string) data_get($taskResult, 'data.USM-AUTHENTICATION', '');
            $usm = (string) data_get($taskResult, 'data.USM', '');

            if ($usmAuthentication === '' || $usm === '') {
                return response()->json([
                    'code' => 502,
                    'message' => 'Token 服务返回数据缺失',
                    'data' => ['task_id' => $taskId, 'result' => $taskResult],
                ], 502);
            }

            $account->update([
                'usm_authentication' => $usmAuthentication,
                'usm' => $usm,
                'last_token_refreshed_at' => now(),
            ]);

            $taskMeta['finished'] = true;
            Cache::put($cacheKey, $taskMeta, now()->addMinutes(10));
            $this->auditLog($request, 'account_refresh_token', ['bastion_account_id' => $account->id, 'task_id' => $taskId]);
        }

        return response()->json([
            'code' => 0,
            'message' => 'Token 刷新成功',
            'data' => ['task_id' => $taskId, 'status' => 'success', 'account' => $account->fresh()],
        ]);
    }

    private function tokenTaskCacheKey(string $taskId): string
    {
        return 'bastion_token_task:'.$taskId;
    }
}
