<?php

use App\Actions\Shared\MigrateResourceToDestination;
use App\Enums\BuildPackTypes;
use App\Enums\RedirectTypes;
use App\Enums\StaticImageTypes;
use App\Models\Environment;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use App\Rules\ValidGitBranch;
use App\Support\ValidationPatterns;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

function getTeamIdFromToken()
{
    $user = auth()->user();
    $token = $user?->currentAccessToken();
    $teamId = data_get($token, 'team_id');

    if (! $user || is_null($teamId) || ! $user->teams()->where('teams.id', $teamId)->exists()) {
        return null;
    }

    return $teamId;
}
function invalidTokenResponse()
{
    return response()->json(['message' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api-reference/authorization'], 400);
}

function serializeApiResponse($data)
{
    if ($data instanceof Collection) {
        return $data->map(function ($d) {
            $d = collect($d)->sortKeys();
            $created_at = data_get($d, 'created_at');
            $updated_at = data_get($d, 'updated_at');
            if ($created_at) {
                unset($d['created_at']);
                $d['created_at'] = $created_at;
            }
            if ($updated_at) {
                unset($d['updated_at']);
                $d['updated_at'] = $updated_at;
            }
            if (data_get($d, 'name')) {
                $d = $d->prepend($d['name'], 'name');
            }
            if (data_get($d, 'description')) {
                $d = $d->prepend($d['description'], 'description');
            }
            if (data_get($d, 'uuid')) {
                $d = $d->prepend($d['uuid'], 'uuid');
            }

            if (! is_null(data_get($d, 'id'))) {
                $d = $d->prepend($d['id'], 'id');
            }

            return $d;
        });
    } else {
        $d = collect($data)->sortKeys();
        $created_at = data_get($d, 'created_at');
        $updated_at = data_get($d, 'updated_at');
        if ($created_at) {
            unset($d['created_at']);
            $d['created_at'] = $created_at;
        }
        if ($updated_at) {
            unset($d['updated_at']);
            $d['updated_at'] = $updated_at;
        }
        if (data_get($d, 'name')) {
            $d = $d->prepend($d['name'], 'name');
        }
        if (data_get($d, 'description')) {
            $d = $d->prepend($d['description'], 'description');
        }
        if (data_get($d, 'uuid')) {
            $d = $d->prepend($d['uuid'], 'uuid');
        }

        if (! is_null(data_get($d, 'id'))) {
            $d = $d->prepend($d['id'], 'id');
        }

        return $d;
    }
}

/**
 * Re-expose a model's `$hidden` sensitive fields when the current API request
 * carries the `read:sensitive` or `root` token ability (set by the
 * ApiSensitiveData middleware).
 */
function exposeSensitiveFields(Model $model): Model
{
    if (request()->attributes->get('can_read_sensitive', false) === true && filled($model->getHidden())) {
        $model->makeVisible($model->getHidden());
    }

    return $model;
}

function sharedDataApplications()
{
    return [
        'git_repository' => 'string',
        'git_branch' => ['string', new ValidGitBranch],
        'build_pack' => Rule::enum(BuildPackTypes::class),
        'is_static' => 'boolean',
        'is_spa' => 'boolean',
        'is_auto_deploy_enabled' => 'boolean',
        'is_force_https_enabled' => 'boolean',
        'is_preview_deployments_enabled' => 'boolean',
        'use_build_secrets' => 'boolean',
        'is_git_submodules_enabled' => 'boolean',
        'is_git_lfs_enabled' => 'boolean',
        'is_git_shallow_clone_enabled' => 'boolean',
        'disable_build_cache' => 'boolean',
        'inject_build_args_to_dockerfile' => 'boolean',
        'include_source_commit_in_build' => 'boolean',
        'is_env_sorting_enabled' => 'boolean',
        'is_pr_deployments_public_enabled' => 'boolean',
        'is_gzip_enabled' => 'boolean',
        'is_stripprefix_enabled' => 'boolean',
        'is_raw_compose_deployment_enabled' => 'boolean',
        'is_log_drain_enabled' => 'boolean',
        'is_gpu_enabled' => 'boolean',
        'gpu_driver' => 'string|nullable',
        'gpu_count' => 'string|nullable',
        'gpu_device_ids' => 'string|nullable',
        'gpu_options' => 'string|nullable',
        'is_consistent_container_name_enabled' => 'boolean',
        'custom_internal_name' => 'string|nullable',
        'preview_url_template' => 'string',
        'max_restart_count' => 'integer|min:0',
        'stop_grace_period' => 'nullable|integer|min:'.MIN_STOP_GRACE_PERIOD_SECONDS.'|max:'.MAX_STOP_GRACE_PERIOD_SECONDS,
        'docker_images_to_keep' => 'integer|min:0|max:100',
        'static_image' => Rule::enum(StaticImageTypes::class),
        'domains' => ValidationPatterns::applicationDomainRules(),
        'noindex_domains' => 'array|nullable',
        'noindex_domains.*' => 'string',
        'redirect' => Rule::enum(RedirectTypes::class),
        'git_commit_sha' => ['string', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9._\-\/]*$/'],
        'docker_registry_image_name' => ValidationPatterns::dockerImageNameRules(),
        'docker_registry_image_tag' => ValidationPatterns::dockerImageTagRules(),
        'install_command' => ValidationPatterns::shellSafeCommandRules(),
        'build_command' => ValidationPatterns::shellSafeCommandRules(),
        'start_command' => ValidationPatterns::shellSafeCommandRules(),
        'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/',
        'ports_mappings' => 'string|regex:/^(\d+:\d+)(,\d+:\d+)*$/|nullable',
        'custom_network_aliases' => 'string|nullable',
        'base_directory' => ValidationPatterns::directoryPathRules(),
        'publish_directory' => ValidationPatterns::directoryPathRules(),
        'health_check_enabled' => 'boolean',
        'health_check_type' => 'string|in:http,cmd',
        'health_check_command' => ['nullable', 'string', 'max:1000', 'regex:/^[a-zA-Z0-9 \-_.\/:=@,+]+$/'],
        'health_check_path' => ['string', 'regex:#^[a-zA-Z0-9/\-_.~%,;]+$#'],
        'health_check_port' => 'integer|nullable|min:1|max:65535',
        'health_check_host' => ['string', 'regex:/^[a-zA-Z0-9.\-_]+$/'],
        'health_check_method' => 'string|in:GET,HEAD,POST,OPTIONS',
        'health_check_return_code' => 'numeric',
        'health_check_scheme' => 'string|in:http,https',
        'health_check_response_text' => 'string|nullable',
        'health_check_interval' => 'numeric',
        'health_check_timeout' => 'numeric',
        'health_check_retries' => 'numeric',
        'health_check_start_period' => 'numeric',
        'limits_memory' => 'string',
        'limits_memory_swap' => 'string',
        'limits_memory_swappiness' => 'numeric',
        'limits_memory_reservation' => 'string',
        'limits_cpus' => 'string',
        'limits_cpuset' => 'string|nullable',
        'limits_cpu_shares' => 'numeric',
        'custom_labels' => 'string|nullable',
        'custom_docker_run_options' => ValidationPatterns::shellSafeCommandRules(2000),
        // Security: deployment commands are intentionally arbitrary shell (e.g. "php artisan migrate").
        // Access is gated by API token authentication. Commands run inside the app container, not the host.
        'post_deployment_command' => 'string|nullable',
        'post_deployment_command_container' => ValidationPatterns::containerNameRules(),
        'pre_deployment_command' => 'string|nullable',
        'pre_deployment_command_container' => ValidationPatterns::containerNameRules(),
        'manual_webhook_secret_github' => 'string|nullable',
        'manual_webhook_secret_gitlab' => 'string|nullable',
        'manual_webhook_secret_bitbucket' => 'string|nullable',
        'manual_webhook_secret_gitea' => 'string|nullable',
        'dockerfile_location' => ValidationPatterns::filePathRules(),
        'dockerfile_target_build' => ValidationPatterns::dockerTargetRules(),
        'docker_compose_location' => ValidationPatterns::filePathRules(),
        'docker_compose' => 'string|nullable',
        'docker_compose_domains' => 'array|nullable',
        'docker_compose_custom_start_command' => ValidationPatterns::shellSafeCommandRules(),
        'docker_compose_custom_build_command' => ValidationPatterns::shellSafeCommandRules(),
        'is_container_label_escape_enabled' => 'boolean',
        'is_preserve_repository_enabled' => 'boolean',
    ];
}

function moveResourceToEnvironment(Request $request, $resource, string $resourceType, int $teamId): JsonResponse
{

    $validator = Validator::make($request->all(), [
        'environment_uuid' => 'required|string',
    ]);

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

    $extraFields = array_diff(array_keys($request->all()), ['environment_uuid']);
    if (! empty($extraFields)) {
        return response()->json([
            'message' => 'Validation failed.',
            'errors' => collect($extraFields)->mapWithKeys(fn ($field) => [$field => 'This field is not allowed.'])->toArray(),
        ], 422);
    }

    $newEnvironment = Environment::ownedByCurrentTeamAPI($teamId)
        ->whereUuid($request->environment_uuid)
        ->first();

    if (! $newEnvironment) {
        return response()->json(['message' => 'Target environment not found or not owned by your team.'], 404);
    }

    Gate::authorize('update', $newEnvironment);

    if ($resource->environment_id === $newEnvironment->id) {
        return response()->json(['message' => "$resourceType is already in this environment."], 400);
    }

    $oldEnvironment = $resource->environment()->with('project')->first();

    $resource->update(['environment_id' => $newEnvironment->id]);

    auditLog('api.'.str($resourceType)->lower()->value().'.moved', [
        'team_id' => $teamId,
        'resource_uuid' => $resource->uuid,
        'resource_type' => str($resourceType)->lower()->value(),
        'from_project_uuid' => $oldEnvironment?->project?->uuid,
        'from_environment_uuid' => $oldEnvironment?->uuid,
        'to_project_uuid' => $newEnvironment->project->uuid,
        'to_environment_uuid' => $newEnvironment->uuid,
    ]);

    return response()->json([
        'message' => "$resourceType moved successfully.",
        'uuid' => $resource->uuid,
        'project_uuid' => $newEnvironment->project->uuid,
        'environment_uuid' => $newEnvironment->uuid,
    ]);
}

function migrateResourceToDestination(Request $request, $resource, string $resourceType, int $teamId): JsonResponse
{
    if (! isDev()) {
        abort(404);
    }

    $validator = Validator::make($request->all(), [
        'destination_uuid' => 'required|string',
        'migrate_volumes' => 'boolean',
    ]);

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

    $allowedFields = ['destination_uuid', 'migrate_volumes'];
    $extraFields = array_diff(array_keys($request->all()), $allowedFields);
    if (! empty($extraFields)) {
        return response()->json([
            'message' => 'Validation failed.',
            'errors' => collect($extraFields)->mapWithKeys(fn ($field) => [$field => 'This field is not allowed.'])->toArray(),
        ], 422);
    }

    Gate::authorize('update', $resource);

    $destination = StandaloneDocker::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->destination_uuid)->first()
        ?? SwarmDocker::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->destination_uuid)->first();

    if (! $destination || ! $destination->server?->canHostResources()) {
        return response()->json(['message' => 'Destination not found.'], 404);
    }

    $sourceDestination = $resource->destination;
    $migrateVolumes = $request->boolean('migrate_volumes', true);

    try {
        $result = MigrateResourceToDestination::run(
            $resource,
            $destination,
            $migrateVolumes,
        );
    } catch (ValidationException $e) {
        return response()->json([
            'message' => collect($e->errors())->flatten()->first() ?? $e->getMessage(),
            'errors' => $e->errors(),
        ], 422);
    }

    auditLog('api.'.str($resourceType)->lower()->value().'.migrated', [
        'team_id' => $teamId,
        'resource_uuid' => $resource->uuid,
        'resource_type' => str($resourceType)->lower()->value(),
        'from_destination_uuid' => $sourceDestination?->uuid,
        'to_destination_uuid' => $destination->uuid,
        'from_server_id' => $sourceDestination?->server_id,
        'to_server_id' => $destination->server_id,
        'migrate_volumes' => $migrateVolumes,
        'async' => $result['async'],
        'volume_jobs' => $result['volume_jobs'],
    ]);

    return response()->json([
        'message' => $result['message'],
        'uuid' => $resource->uuid,
        'destination_uuid' => $destination->uuid,
        'async' => $result['async'],
        'volume_jobs' => $result['volume_jobs'],
    ]);
}

function validateIncomingRequest(Request $request)
{
    // check if request is json
    if (! $request->isJson()) {
        return response()->json([
            'message' => 'Invalid request.',
            'error' => 'Content-Type must be application/json.',
        ], 400);
    }
    // check if request is valid json
    if (! json_decode($request->getContent())) {
        return response()->json([
            'message' => 'Invalid request.',
            'error' => 'Invalid JSON.',
        ], 400);
    }
    // check if valid json is empty
    if (empty($request->json()->all())) {
        return response()->json([
            'message' => 'Invalid request.',
            'error' => 'Empty JSON.',
        ], 400);
    }
}

function removeUnnecessaryFieldsFromRequest(Request $request)
{
    $request->offsetUnset('project_uuid');
    $request->offsetUnset('environment_name');
    $request->offsetUnset('environment_uuid');
    $request->offsetUnset('destination_uuid');
    $request->offsetUnset('server_uuid');
    $request->offsetUnset('type');
    $request->offsetUnset('domains');
    $request->offsetUnset('instant_deploy');
    $request->offsetUnset('github_app_uuid');
    $request->offsetUnset('private_key_uuid');
    $request->offsetUnset('use_build_server');
    $request->offsetUnset('use_build_secrets');
    $request->offsetUnset('is_static');
    $request->offsetUnset('is_spa');
    $request->offsetUnset('is_auto_deploy_enabled');
    $request->offsetUnset('is_force_https_enabled');
    $request->offsetUnset('is_preview_deployments_enabled');
    $request->offsetUnset('connect_to_docker_network');
    $request->offsetUnset('force_domain_override');
    $request->offsetUnset('autogenerate_domain');
    $request->offsetUnset('is_container_label_escape_enabled');
    $request->offsetUnset('is_preserve_repository_enabled');
    $request->offsetUnset('include_source_commit_in_build');
    $request->offsetUnset('is_git_submodules_enabled');
    $request->offsetUnset('is_git_lfs_enabled');
    $request->offsetUnset('is_git_shallow_clone_enabled');
    $request->offsetUnset('disable_build_cache');
    $request->offsetUnset('inject_build_args_to_dockerfile');
    $request->offsetUnset('is_env_sorting_enabled');
    $request->offsetUnset('is_pr_deployments_public_enabled');
    $request->offsetUnset('stop_grace_period');
    $request->offsetUnset('docker_images_to_keep');
    $request->offsetUnset('is_gzip_enabled');
    $request->offsetUnset('is_stripprefix_enabled');
    $request->offsetUnset('is_raw_compose_deployment_enabled');
    $request->offsetUnset('is_log_drain_enabled');
    $request->offsetUnset('is_gpu_enabled');
    $request->offsetUnset('gpu_driver');
    $request->offsetUnset('gpu_count');
    $request->offsetUnset('gpu_device_ids');
    $request->offsetUnset('gpu_options');
    $request->offsetUnset('is_consistent_container_name_enabled');
    $request->offsetUnset('custom_internal_name');
    $request->offsetUnset('docker_compose_raw');
    $request->offsetUnset('tags');
}
