<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\AccessLog;
use App\Models\OpsProtocol;
use App\Models\OpsSoftware;
use App\Models\User;
use App\Models\UserOpsSoftwarePreference;
use hg\apidoc\annotation as Apidoc;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

#[Apidoc\Title('运维协议与软件管理')]
class OpsClientController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth:api');
        $this->middleware('permission:platform.servers.manage,api')->only([
            'storeProtocol',
            'updateProtocol',
            'destroyProtocol',
            'storeSoftware',
            'updateSoftware',
            'destroySoftware',
        ]);
    }

    #[Apidoc\Title('运维协议与软件列表'), Apidoc\Method('GET'), Apidoc\Url('/ops-clients/meta')]
    public function meta(Request $request): JsonResponse
    {
        /** @var User|null $user */
        $user = auth('api')->user();
        $isManager = (bool) ($user?->can('platform.servers.manage'));

        $protocolQuery = OpsProtocol::query()->orderBy('sort')->orderBy('id');
        if (! $isManager) {
            $protocolQuery->where('is_active', true);
        }

        $protocols = $protocolQuery->with(['softwares' => function ($query) use ($isManager) {
            if (! $isManager) {
                $query->where('is_active', true);
            }
        }])->get();

        $preferences = UserOpsSoftwarePreference::query()
            ->where('user_id', $user?->id)
            ->get()
            ->mapWithKeys(fn (UserOpsSoftwarePreference $item): array => [
                (string) $item->ops_protocol_id => $item->ops_software_id,
            ]);

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

    #[Apidoc\Title('新增运维协议'), Apidoc\Method('POST'), Apidoc\Url('/ops-clients/protocols')]
    public function storeProtocol(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'name' => ['required', 'string', 'max:64', 'unique:ops_protocols,name'],
            'bastion_protocol_id' => ['required', 'integer', 'min:1'],
            'description' => ['nullable', 'string', 'max:255'],
            'sort' => ['sometimes', 'integer', 'min:0'],
            'is_active' => ['sometimes', 'boolean'],
        ]);

        $protocol = OpsProtocol::query()->create($validated);
        $this->auditLog($request, 'ops_protocol_create', ['metadata' => ['ops_protocol_id' => $protocol->id]]);

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

    #[Apidoc\Title('修改运维协议'), Apidoc\Method('PUT'), Apidoc\Url('/ops-clients/protocols/{id}')]
    public function updateProtocol(Request $request, int $id): JsonResponse
    {
        $protocol = OpsProtocol::query()->findOrFail($id);
        $validated = $request->validate([
            'name' => ['required', 'string', 'max:64', 'unique:ops_protocols,name,'.$protocol->id],
            'bastion_protocol_id' => ['required', 'integer', 'min:1'],
            'description' => ['nullable', 'string', 'max:255'],
            'sort' => ['sometimes', 'integer', 'min:0'],
            'is_active' => ['sometimes', 'boolean'],
        ]);

        $protocol->update($validated);
        $this->auditLog($request, 'ops_protocol_update', ['metadata' => ['ops_protocol_id' => $protocol->id]]);

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

    #[Apidoc\Title('删除运维协议'), Apidoc\Method('DELETE'), Apidoc\Url('/ops-clients/protocols/{id}')]
    public function destroyProtocol(Request $request, int $id): JsonResponse
    {
        $protocol = OpsProtocol::query()->findOrFail($id);
        $this->auditLog($request, 'ops_protocol_delete', ['metadata' => ['ops_protocol_id' => $protocol->id]]);
        $protocol->delete();

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

    #[Apidoc\Title('新增运维软件'), Apidoc\Method('POST'), Apidoc\Url('/ops-clients/protocols/{id}/softwares')]
    public function storeSoftware(Request $request, int $id): JsonResponse
    {
        OpsProtocol::query()->findOrFail($id);
        $validated = $request->validate([
            'name' => ['required', 'string', 'max:100'],
            'client_path' => ['nullable', 'string', 'max:255'],
            'sort' => ['sometimes', 'integer', 'min:0'],
            'is_active' => ['sometimes', 'boolean'],
        ]);

        $exists = OpsSoftware::query()
            ->where('ops_protocol_id', $id)
            ->where('name', $validated['name'])
            ->exists();
        if ($exists) {
            throw ValidationException::withMessages([
                'name' => ['同一协议下软件名称不能重复。'],
            ]);
        }

        $software = OpsSoftware::query()->create([
            ...$validated,
            'ops_protocol_id' => $id,
        ]);
        $this->auditLog($request, 'ops_software_create', ['metadata' => ['ops_software_id' => $software->id, 'ops_protocol_id' => $id]]);

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

    #[Apidoc\Title('修改运维软件'), Apidoc\Method('PUT'), Apidoc\Url('/ops-clients/softwares/{id}')]
    public function updateSoftware(Request $request, int $id): JsonResponse
    {
        $software = OpsSoftware::query()->findOrFail($id);
        $validated = $request->validate([
            'name' => ['required', 'string', 'max:100'],
            'client_path' => ['nullable', 'string', 'max:255'],
            'sort' => ['sometimes', 'integer', 'min:0'],
            'is_active' => ['sometimes', 'boolean'],
        ]);

        $exists = OpsSoftware::query()
            ->where('ops_protocol_id', $software->ops_protocol_id)
            ->where('name', $validated['name'])
            ->where('id', '!=', $software->id)
            ->exists();
        if ($exists) {
            throw ValidationException::withMessages([
                'name' => ['同一协议下软件名称不能重复。'],
            ]);
        }

        $software->update($validated);
        $this->auditLog($request, 'ops_software_update', ['metadata' => ['ops_software_id' => $software->id]]);

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

    #[Apidoc\Title('删除运维软件'), Apidoc\Method('DELETE'), Apidoc\Url('/ops-clients/softwares/{id}')]
    public function destroySoftware(Request $request, int $id): JsonResponse
    {
        $software = OpsSoftware::query()->findOrFail($id);
        $this->auditLog($request, 'ops_software_delete', ['metadata' => ['ops_software_id' => $software->id]]);
        $software->delete();

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

    #[Apidoc\Title('保存我的运维软件偏好'), Apidoc\Method('PUT'), Apidoc\Url('/ops-clients/preferences')]
    public function savePreferences(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'items' => ['required', 'array'],
            'items.*.protocol_id' => ['required', 'integer', 'exists:ops_protocols,id'],
            'items.*.software_id' => ['nullable', 'integer', 'exists:ops_softwares,id'],
        ]);

        /** @var User $user */
        $user = auth('api')->user();

        foreach ($validated['items'] as $item) {
            $protocolId = (int) $item['protocol_id'];
            $softwareId = isset($item['software_id']) ? (int) $item['software_id'] : null;

            if ($softwareId !== null) {
                $software = OpsSoftware::query()->findOrFail($softwareId);
                if ((int) $software->ops_protocol_id !== $protocolId) {
                    throw ValidationException::withMessages([
                        'items' => ['软件与协议不匹配。'],
                    ]);
                }

                UserOpsSoftwarePreference::query()->updateOrCreate(
                    ['user_id' => $user->id, 'ops_protocol_id' => $protocolId],
                    ['ops_software_id' => $softwareId]
                );
            } else {
                UserOpsSoftwarePreference::query()
                    ->where('user_id', $user->id)
                    ->where('ops_protocol_id', $protocolId)
                    ->delete();
            }
        }

        $this->auditLog($request, 'ops_preference_update', ['metadata' => ['user_id' => $user->id]]);

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

    #[Apidoc\Title('生成运维连接'), Apidoc\Method('POST'), Apidoc\Url('/ops-clients/link')]
    public function generateLink(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'protocol_id' => ['required', 'integer', 'exists:ops_protocols,id'],
            'software_id' => ['nullable', 'integer', 'exists:ops_softwares,id'],
        ]);

        /** @var User $user */
        $user = auth('api')->user();
        $protocol = OpsProtocol::query()->findOrFail((int) $validated['protocol_id']);
        $softwareId = isset($validated['software_id']) ? (int) $validated['software_id'] : null;

        if ($softwareId === null) {
            $softwareId = (int) UserOpsSoftwarePreference::query()
                ->where('user_id', $user->id)
                ->where('ops_protocol_id', $protocol->id)
                ->value('ops_software_id');
        }

        $software = OpsSoftware::query()->find($softwareId ?: 0);
        if (! $software || (int) $software->ops_protocol_id !== (int) $protocol->id) {
            throw ValidationException::withMessages([
                'software_id' => ['请先选择该协议对应的软件。'],
            ]);
        }

        $payload = [
            'NODE_COMMON' => [
                'Mode' => '1',
                'IsGlobalSetting' => '0',
                'IPv4' => (string) config('services.ops_client.ipv4', '172.16.1.2'),
                'AssetIPv4' => (string) config('services.ops_client.asset_ipv4', '0.0.0.0'),
                'Port' => '',
                'AssetPort' => '',
                'Protocol' => (string) $protocol->name,
                'ClientName' => (string) $software->name,
                'ClientPath' => (string) ($software->client_path ?? ''),
                'Username' => '',
                'SSOToken' => '',
                'Title' => '',
            ],
            'NODE_SFTP' => [
                'NotUsed' => '1',
                'Char-Set' => 'Utf=1',
            ],
        ];

        $encoded = base64_encode((string) json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
        $link = 'dasusm://'.$encoded;

        UserOpsSoftwarePreference::query()->updateOrCreate(
            ['user_id' => $user->id, 'ops_protocol_id' => $protocol->id],
            ['ops_software_id' => $software->id]
        );

        AccessLog::query()->create([
            'user_id' => $user->id,
            'server_resource_id' => null,
            'bastion_account_id' => null,
            'protocol' => (string) $protocol->name,
            'action' => 'ops_client_link',
            'requested_at' => now(),
            'metadata' => [
                'ops_protocol_id' => $protocol->id,
                'ops_software_id' => $software->id,
                'ops_software_name' => $software->name,
            ],
        ]);

        $this->auditLog($request, 'ops_client_link_generate', [
            'metadata' => [
                'ops_protocol_id' => $protocol->id,
                'ops_software_id' => $software->id,
            ],
        ]);

        return response()->json([
            'code' => 0,
            'message' => 'ok',
            'data' => [
                'link' => $link,
                'encoded' => $encoded,
                'payload' => $payload,
                'protocol' => $protocol,
                'software' => $software,
            ],
        ]);
    }
}
