<?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\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\JsonResponse;
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', 'checkBastionLogin']);
    }

    #[Apidoc\Title('账号列表'), Apidoc\Method('GET'), Apidoc\Url('/accounts')]
    public function index(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
        ]);
        $perPage = (int) ($validated['per_page'] ?? 20);

        return response()->json(['code' => 0, 'message' => 'ok', 'data' => BastionAccount::query()->latest()->paginate($perPage)]);
    }

    #[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);
        $service = (string) config('services.bastion_token.service', 'https://myapp.cdu.edu.cn/index.html');
        $verifySsl = (bool) config('services.bastion_token.verify_ssl', false);

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

            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.bastion.token_cookies.USM-AUTHENTICATION', '');
            $usm = (string) data_get($taskResult, 'data.bastion.token_cookies.USM', '');

            // 向后兼容旧版任务服务返回结构
            if ($usmAuthentication === '' || $usm === '') {
                $usmAuthentication = (string) data_get($taskResult, 'data.USM-AUTHENTICATION', $usmAuthentication);
                $usm = (string) data_get($taskResult, 'data.USM', $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;
    }

    #[Apidoc\Title('检测堡垒机登录有效性'), Apidoc\Method('GET'), Apidoc\Url('/accounts/check-login')]
    public function checkBastionLogin(Request $request): JsonResponse
    {
        $autoRefresh = $request->boolean('auto_refresh', false);
        $accounts = BastionAccount::query()
            ->where('is_active', true)
            ->whereNotNull('usm')
            ->where('usm', '!=', '')
            ->whereNotNull('usm_authentication')
            ->where('usm_authentication', '!=', '')
            ->orderBy('id')
            ->get();

        $result = [];
        foreach ($accounts as $account) {
            $status = $this->checkAccountTokenStatus((string) $account->usm_authentication, (string) $account->usm);
            $refreshed = false;
            if ($status === -1 && $autoRefresh) {
                $refreshed = $this->refreshAccountTokenDirectly($account);
                if ($refreshed) {
                    $account->refresh();
                    $status = $this->checkAccountTokenStatus((string) $account->usm_authentication, (string) $account->usm);
                }
            }

            $result[] = [
                'id' => $account->id,
                'name' => $account->name,
                'username' => $account->username,
                'status' => $status,
                'refreshed' => $refreshed,
            ];
        }

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

    private function checkAccountTokenStatus(string $usmAuthentication, string $usm): int
    {
        $url = 'https://172.16.254.2/index.php/extend/check_login';
        $cookie = sprintf(
            'LANG=zh; USM-AUTHENTICATION=%s; USM=%s; LOGON=1; AUTH_METHOD=oauth2',
            $usmAuthentication,
            $usm
        );

        try {
            $response = Http::timeout(10)
                ->withOptions(['verify' => false])
                ->withHeaders([
                    'Cookie' => $cookie,
                    'Accept' => '*/*',
                ])
                ->get($url);
        } catch (ConnectionException) {
            return -1;
        }

        if (! $response->successful()) {
            return -1;
        }

        return trim((string) $response->body()) === '0' ? 0 : -1;
    }

    private function refreshAccountTokenDirectly(BastionAccount $account): bool
    {
        $baseUrl = (string) config('services.bastion_token.base_url');
        if ($baseUrl === '') {
            return false;
        }

        $submitEndpoint = (string) config('services.bastion_token.submit_endpoint', '/bastion_token');
        $statusEndpoint = (string) config('services.bastion_token.status_endpoint', '/bastion_token/{task_id}');
        $timeout = (int) config('services.bastion_token.timeout', 30);
        $service = (string) config('services.bastion_token.service', 'https://myapp.cdu.edu.cn/index.html');
        $verifySsl = (bool) config('services.bastion_token.verify_ssl', false);

        try {
            $submitResponse = Http::baseUrl($baseUrl)
                ->acceptJson()
                ->timeout($timeout)
                ->retry(2, 300, throw: false)
                ->post($submitEndpoint, [
                    'username' => $account->username,
                    'password' => $account->password,
                    'service' => $service,
                    'verify_ssl' => $verifySsl,
                ]);
        } catch (ConnectionException|RequestException) {
            return false;
        }

        if (! $submitResponse->successful()) {
            return false;
        }

        $taskId = (string) data_get($submitResponse->json(), 'task_id', '');
        if ($taskId === '') {
            return false;
        }

        for ($i = 0; $i < 20; $i++) {
            usleep(500000);
            $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) {
                continue;
            }

            if (! $statusResponse->successful()) {
                continue;
            }

            $taskResult = $statusResponse->json();
            $status = strtolower((string) data_get($taskResult, 'status', 'pending'));
            if ($status === 'pending') {
                continue;
            }
            if ($status === 'error') {
                return false;
            }

            $usmAuthentication = (string) data_get($taskResult, 'data.bastion.token_cookies.USM-AUTHENTICATION', '');
            $usm = (string) data_get($taskResult, 'data.bastion.token_cookies.USM', '');
            if ($usmAuthentication === '' || $usm === '') {
                $usmAuthentication = (string) data_get($taskResult, 'data.USM-AUTHENTICATION', $usmAuthentication);
                $usm = (string) data_get($taskResult, 'data.USM', $usm);
            }
            if ($usmAuthentication === '' || $usm === '') {
                return false;
            }

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

            return true;
        }

        return false;
    }
}
