<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\GitlabApp;
use App\Rules\SafeExternalUrl;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use OpenApi\Attributes as OA;

class GitlabController extends Controller
{
    private function removeSensitiveData(GitlabApp $gitlabApp)
    {
        if (request()->attributes->get('can_read_sensitive', false) === true) {
            $gitlabApp->makeVisible([
                'client_secret',
                'webhook_token',
                'access_token',
                'refresh_token',
            ]);
        } else {
            $gitlabApp->makeHidden([
                'client_secret',
                'webhook_token',
                'access_token',
                'refresh_token',
            ]);
        }

        return serializeApiResponse($gitlabApp);
    }

    private function findTeamGitlabApp(int|string $gitlabAppId, int $teamId): GitlabApp
    {
        return GitlabApp::where('id', $gitlabAppId)
            ->where('team_id', $teamId)
            ->firstOrFail();
    }

    private function gitlabApiUrlFromHtmlUrl(string $htmlUrl): string
    {
        return rtrim($htmlUrl, '/').'/api/v4';
    }

    #[OA\Get(
        summary: 'List',
        description: 'List all GitLab apps for the current team (and system-wide sources).',
        path: '/gitlab-apps',
        operationId: 'list-gitlab-apps',
        security: [
            ['bearerAuth' => []],
        ],
        tags: ['GitLab Apps'],
        responses: [
            new OA\Response(
                response: 200,
                description: 'List of GitLab apps.',
                content: [
                    new OA\MediaType(
                        mediaType: 'application/json',
                        schema: new OA\Schema(
                            type: 'array',
                            items: new OA\Items(
                                type: 'object',
                                properties: [
                                    'id' => ['type' => 'integer'],
                                    'uuid' => ['type' => 'string'],
                                    'name' => ['type' => 'string'],
                                    'api_url' => ['type' => 'string'],
                                    'html_url' => ['type' => 'string'],
                                    'custom_user' => ['type' => 'string'],
                                    'custom_port' => ['type' => 'integer'],
                                    'client_id' => ['type' => 'string', 'nullable' => true],
                                    'group_name' => ['type' => 'string', 'nullable' => true],
                                    'redirect_uri' => ['type' => 'string', 'nullable' => true],
                                    'is_system_wide' => ['type' => 'boolean'],
                                    'is_public' => ['type' => 'boolean'],
                                    'team_id' => ['type' => 'integer'],
                                ]
                            )
                        )
                    ),
                ]
            ),
            new OA\Response(
                response: 401,
                ref: '#/components/responses/401',
            ),
            new OA\Response(
                response: 400,
                ref: '#/components/responses/400',
            ),
        ]
    )]
    public function list_gitlab_apps(Request $request)
    {
        $teamId = getTeamIdFromToken();
        if (is_null($teamId)) {
            return invalidTokenResponse();
        }

        $gitlabApps = GitlabApp::where(function ($query) use ($teamId) {
            $query->where('team_id', $teamId)
                ->orWhere('is_system_wide', true);
        })->get();

        $gitlabApps = $gitlabApps->map(function ($app) {
            return $this->removeSensitiveData($app);
        });

        return response()->json($gitlabApps);
    }

    #[OA\Post(
        summary: 'Create GitLab App',
        description: 'Create a new GitLab app (OAuth source). Credentials may be supplied later via the UI or update endpoint.',
        path: '/gitlab-apps',
        operationId: 'create-gitlab-app',
        security: [
            ['bearerAuth' => []],
        ],
        tags: ['GitLab Apps'],
        requestBody: new OA\RequestBody(
            description: 'GitLab app creation payload.',
            required: true,
            content: [
                new OA\MediaType(
                    mediaType: 'application/json',
                    schema: new OA\Schema(
                        type: 'object',
                        properties: [
                            'name' => ['type' => 'string', 'description' => 'Name of the GitLab app.'],
                            'html_url' => ['type' => 'string', 'description' => 'GitLab instance URL (e.g., https://gitlab.com).'],
                            'api_url' => ['type' => 'string', 'description' => 'GitLab API URL (defaults to {html_url}/api/v4).'],
                            'custom_user' => ['type' => 'string', 'description' => 'Custom user for SSH access (default: git).'],
                            'custom_port' => ['type' => 'integer', 'description' => 'Custom port for SSH access (default: 22).'],
                            'group_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Optional comma-separated group names to filter repositories.'],
                            'client_id' => ['type' => 'string', 'nullable' => true, 'description' => 'GitLab OAuth Application ID.'],
                            'client_secret' => ['type' => 'string', 'nullable' => true, 'description' => 'GitLab OAuth Application Secret.'],
                            'webhook_token' => ['type' => 'string', 'nullable' => true, 'description' => 'Webhook secret token (auto-generated when omitted).'],
                            'redirect_uri' => ['type' => 'string', 'nullable' => true, 'description' => 'OAuth redirect URI registered in GitLab.'],
                            'is_system_wide' => ['type' => 'boolean', 'description' => 'Is this app system-wide (non-cloud instances only).'],
                        ],
                        required: ['name', 'html_url'],
                    ),
                ),
            ],
        ),
        responses: [
            new OA\Response(
                response: 201,
                description: 'GitLab app created successfully.',
                content: [
                    new OA\MediaType(
                        mediaType: 'application/json',
                        schema: new OA\Schema(
                            type: 'object',
                            properties: [
                                'id' => ['type' => 'integer'],
                                'uuid' => ['type' => 'string'],
                                'name' => ['type' => 'string'],
                                'api_url' => ['type' => 'string'],
                                'html_url' => ['type' => 'string'],
                                'custom_user' => ['type' => 'string'],
                                'custom_port' => ['type' => 'integer'],
                                'client_id' => ['type' => 'string', 'nullable' => true],
                                'group_name' => ['type' => 'string', 'nullable' => true],
                                'redirect_uri' => ['type' => 'string', 'nullable' => true],
                                'is_system_wide' => ['type' => 'boolean'],
                                'team_id' => ['type' => 'integer'],
                            ]
                        )
                    ),
                ]
            ),
            new OA\Response(
                response: 400,
                ref: '#/components/responses/400',
            ),
            new OA\Response(
                response: 401,
                ref: '#/components/responses/401',
            ),
            new OA\Response(
                response: 422,
                ref: '#/components/responses/422',
            ),
        ]
    )]
    public function create_gitlab_app(Request $request)
    {
        $teamId = getTeamIdFromToken();
        if (is_null($teamId)) {
            return invalidTokenResponse();
        }
        $this->authorize('create', [GitlabApp::class]);
        $return = validateIncomingRequest($request);
        if ($return instanceof JsonResponse) {
            return $return;
        }

        $allowedFields = [
            'name',
            'html_url',
            'api_url',
            'custom_user',
            'custom_port',
            'group_name',
            'client_id',
            'client_secret',
            'webhook_token',
            'redirect_uri',
            'is_system_wide',
        ];

        $validator = customApiValidator($request->all(), [
            'name' => 'required|string|max:255',
            'html_url' => ['required', 'string', 'url', new SafeExternalUrl],
            'api_url' => ['nullable', 'string', 'url', new SafeExternalUrl],
            'custom_user' => 'nullable|string|max:255',
            'custom_port' => 'nullable|integer|min:1|max:65535',
            'group_name' => 'nullable|string|max:255',
            'client_id' => 'nullable|string|max:255',
            'client_secret' => 'nullable|string',
            'webhook_token' => 'nullable|string',
            // Callback to this Coolify instance — may be a private/LAN URL; do not use SafeExternalUrl.
            'redirect_uri' => ['nullable', 'string', 'url'],
            'is_system_wide' => 'boolean',
        ]);

        $extraFields = array_diff(array_keys($request->all()), $allowedFields);
        if ($validator->fails() || ! empty($extraFields)) {
            $errors = $validator->errors();
            if (! empty($extraFields)) {
                foreach ($extraFields as $field) {
                    $errors->add($field, 'This field is not allowed.');
                }
            }

            return response()->json([
                'message' => 'Validation failed.',
                'errors' => $errors,
            ], 422);
        }

        try {
            $htmlUrl = rtrim((string) $request->input('html_url'), '/');
            $apiUrl = filled($request->input('api_url'))
                ? rtrim((string) $request->input('api_url'), '/')
                : $this->gitlabApiUrlFromHtmlUrl($htmlUrl);

            $payload = [
                'name' => $request->input('name'),
                'html_url' => $htmlUrl,
                'api_url' => $apiUrl,
                'custom_user' => $request->input('custom_user', 'git'),
                'custom_port' => $request->input('custom_port', 22),
                'group_name' => $request->input('group_name'),
                'client_id' => $request->input('client_id'),
                'client_secret' => $request->input('client_secret'),
                'webhook_token' => $request->input('webhook_token') ?: Str::random(32),
                'redirect_uri' => $request->input('redirect_uri'),
                'is_public' => false,
                'team_id' => $teamId,
            ];

            if (! isCloud()) {
                $payload['is_system_wide'] = $request->boolean('is_system_wide', false);
            }

            $gitlabApp = GitlabApp::create($payload);

            auditLog('api.gitlab_app.created', [
                'team_id' => $teamId,
                'gitlab_app_uuid' => $gitlabApp->uuid,
                'gitlab_app_name' => $gitlabApp->name,
            ]);

            return response()->json($this->removeSensitiveData($gitlabApp->fresh()), 201);
        } catch (\Throwable $e) {
            return handleError($e);
        }
    }

    #[OA\Patch(
        path: '/gitlab-apps/{gitlab_app_id}',
        operationId: 'updateGitlabApp',
        security: [
            ['bearerAuth' => []],
        ],
        tags: ['GitLab Apps'],
        summary: 'Update GitLab App',
        description: 'Update an existing GitLab app.',
        parameters: [
            new OA\Parameter(
                name: 'gitlab_app_id',
                in: 'path',
                required: true,
                schema: new OA\Schema(type: 'integer'),
                description: 'GitLab App ID'
            ),
        ],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\MediaType(
                mediaType: 'application/json',
                schema: new OA\Schema(
                    type: 'object',
                    properties: [
                        'name' => ['type' => 'string', 'description' => 'GitLab App name'],
                        'html_url' => ['type' => 'string', 'description' => 'GitLab HTML URL'],
                        'api_url' => ['type' => 'string', 'description' => 'GitLab API URL'],
                        'custom_user' => ['type' => 'string', 'description' => 'Custom user for SSH'],
                        'custom_port' => ['type' => 'integer', 'description' => 'Custom port for SSH'],
                        'group_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Optional group filter'],
                        'client_id' => ['type' => 'string', 'nullable' => true, 'description' => 'OAuth Application ID'],
                        'client_secret' => ['type' => 'string', 'nullable' => true, 'description' => 'OAuth Application Secret'],
                        'webhook_token' => ['type' => 'string', 'nullable' => true, 'description' => 'Webhook secret token'],
                        'redirect_uri' => ['type' => 'string', 'nullable' => true, 'description' => 'OAuth redirect URI'],
                        'is_system_wide' => ['type' => 'boolean', 'description' => 'Is system wide (non-cloud instances only)'],
                    ]
                )
            )
        ),
        responses: [
            new OA\Response(
                response: 200,
                description: 'GitLab app updated successfully',
                content: new OA\MediaType(
                    mediaType: 'application/json',
                    schema: new OA\Schema(
                        type: 'object',
                        properties: [
                            'message' => ['type' => 'string', 'example' => 'GitLab app updated successfully'],
                            'data' => ['type' => 'object', 'description' => 'Updated GitLab app data'],
                        ]
                    )
                )
            ),
            new OA\Response(response: 401, description: 'Unauthorized'),
            new OA\Response(response: 404, description: 'GitLab app not found'),
            new OA\Response(response: 422, ref: '#/components/responses/422'),
        ]
    )]
    public function update_gitlab_app(Request $request, $gitlab_app_id)
    {
        $teamId = getTeamIdFromToken();
        if (is_null($teamId)) {
            return invalidTokenResponse();
        }

        try {
            $gitlabApp = $this->findTeamGitlabApp($gitlab_app_id, $teamId);
            $this->authorize('update', $gitlabApp);

            $allowedFields = [
                'name',
                'html_url',
                'api_url',
                'custom_user',
                'custom_port',
                'group_name',
                'client_id',
                'client_secret',
                'webhook_token',
                'redirect_uri',
            ];

            if (! isCloud()) {
                $allowedFields[] = 'is_system_wide';
            }

            $payload = $request->only($allowedFields);

            $rules = [];
            if (isset($payload['name'])) {
                $rules['name'] = 'string|max:255';
            }
            if (isset($payload['html_url'])) {
                $rules['html_url'] = ['url', new SafeExternalUrl];
            }
            if (isset($payload['api_url'])) {
                $rules['api_url'] = ['url', new SafeExternalUrl];
            }
            if (isset($payload['custom_user'])) {
                $rules['custom_user'] = 'string|max:255';
            }
            if (isset($payload['custom_port'])) {
                $rules['custom_port'] = 'integer|min:1|max:65535';
            }
            if (array_key_exists('group_name', $payload)) {
                $rules['group_name'] = 'nullable|string|max:255';
            }
            if (array_key_exists('client_id', $payload)) {
                $rules['client_id'] = 'nullable|string|max:255';
            }
            if (array_key_exists('client_secret', $payload)) {
                $rules['client_secret'] = 'nullable|string';
            }
            if (array_key_exists('webhook_token', $payload)) {
                $rules['webhook_token'] = 'nullable|string';
            }
            if (array_key_exists('redirect_uri', $payload)) {
                // Callback to this Coolify instance — may be a private/LAN URL.
                $rules['redirect_uri'] = 'nullable|url';
            }
            if (! isCloud() && isset($payload['is_system_wide'])) {
                $rules['is_system_wide'] = 'boolean';
            }

            $validator = customApiValidator($payload, $rules);
            if ($validator->fails()) {
                return response()->json([
                    'message' => 'Validation error',
                    'errors' => $validator->errors(),
                ], 422);
            }

            if (isset($payload['html_url'])) {
                $payload['html_url'] = rtrim((string) $payload['html_url'], '/');
                if (! filled($payload['api_url'] ?? null)) {
                    $payload['api_url'] = $this->gitlabApiUrlFromHtmlUrl($payload['html_url']);
                }
            }
            if (isset($payload['api_url'])) {
                $payload['api_url'] = rtrim((string) $payload['api_url'], '/');
            }

            $gitlabApp->update($payload);

            auditLog('api.gitlab_app.updated', [
                'team_id' => $teamId,
                'gitlab_app_uuid' => $gitlabApp->uuid,
                'gitlab_app_name' => $gitlabApp->name,
                'changed_fields' => array_values(array_diff(array_keys($payload), ['client_secret', 'webhook_token'])),
            ]);

            return response()->json([
                'message' => 'GitLab app updated successfully',
                'data' => $this->removeSensitiveData($gitlabApp->fresh()),
            ]);
        } catch (ModelNotFoundException $e) {
            return response()->json([
                'message' => 'GitLab app not found',
            ], 404);
        }
    }

    #[OA\Delete(
        path: '/gitlab-apps/{gitlab_app_id}',
        operationId: 'deleteGitlabApp',
        security: [
            ['bearerAuth' => []],
        ],
        tags: ['GitLab Apps'],
        summary: 'Delete GitLab App',
        description: 'Delete a GitLab app if it is not being used by any applications.',
        parameters: [
            new OA\Parameter(
                name: 'gitlab_app_id',
                in: 'path',
                required: true,
                schema: new OA\Schema(type: 'integer'),
                description: 'GitLab App ID'
            ),
        ],
        responses: [
            new OA\Response(
                response: 200,
                description: 'GitLab app deleted successfully',
                content: new OA\MediaType(
                    mediaType: 'application/json',
                    schema: new OA\Schema(
                        type: 'object',
                        properties: [
                            'message' => ['type' => 'string', 'example' => 'GitLab app deleted successfully'],
                        ]
                    )
                )
            ),
            new OA\Response(response: 401, description: 'Unauthorized'),
            new OA\Response(response: 404, description: 'GitLab app not found'),
            new OA\Response(
                response: 409,
                description: 'Conflict - GitLab app is in use',
                content: new OA\MediaType(
                    mediaType: 'application/json',
                    schema: new OA\Schema(
                        type: 'object',
                        properties: [
                            'message' => ['type' => 'string', 'example' => 'This GitLab app is being used by 5 application(s). Please delete all applications first.'],
                        ]
                    )
                )
            ),
        ]
    )]
    public function delete_gitlab_app($gitlab_app_id)
    {
        $teamId = getTeamIdFromToken();
        if (is_null($teamId)) {
            return invalidTokenResponse();
        }

        try {
            $gitlabApp = $this->findTeamGitlabApp($gitlab_app_id, $teamId);
            $this->authorize('delete', $gitlabApp);

            if ($gitlabApp->applications->isNotEmpty()) {
                $count = $gitlabApp->applications->count();

                return response()->json([
                    'message' => "This GitLab app is being used by {$count} application(s). Please delete all applications first.",
                ], 409);
            }

            $deletedUuid = $gitlabApp->uuid;
            $deletedName = $gitlabApp->name;
            $gitlabApp->delete();

            auditLog('api.gitlab_app.deleted', [
                'team_id' => $teamId,
                'gitlab_app_uuid' => $deletedUuid,
                'gitlab_app_name' => $deletedName,
            ]);

            return response()->json([
                'message' => 'GitLab app deleted successfully',
            ]);
        } catch (ModelNotFoundException $e) {
            return response()->json([
                'message' => 'GitLab app not found',
            ], 404);
        }
    }
}
