<?php

namespace App\Master;

use App\Master\Models\ApiToken;
use App\Master\Models\ApiUser;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Str;

class ApiUserRepository
{
    public function update(ApiUser $user, array $input): void
    {
        $user->update([
            'name' => $input['name'] ?? '',
            'is_active' => $input['is_active'] ?? false,
            'token_type' => $input['token_type'] ?? ApiToken::TYPE_OAUTH,
            'notes' => $input['notes'] ?? null,
        ]);
        if ($input['token_type'] !== ApiToken::TYPE_OAUTH) {
            $user->update([
                'client_id' => null,
                'client_secret' => null,
            ]);
        }
        $tenantIds = collect($input['tenants'] ?? [])->pluck('id')->all();
        $user->tenants()->sync($tenantIds);
    }

    public function createAuthCredentials(ApiUser $user): void
    {
        match ($user->token_type) {
            'persistent' => $this->createStaticToken($user),
            default => $this->createOAuthCredentials($user)
        };
    }

    public function expireToken(ApiToken $token): void
    {
        $token->update([
            'expires_at' => Carbon::now(),
        ]);
    }

    public function getAccessToken(string $client_id, string $client_secret): ApiToken
    {
        $user = ApiUser::query()
            ->where('is_active', 1)
            ->where(function ($query) use ($client_id) {
                $query->where('client_id', $client_id)
                    ->orWhere(function ($q) use ($client_id) {
                        $q->where('previous_client_id', $client_id)
                            ->where('previous_credentials_expires_at', '>', Carbon::now());
                    });
            })
            ->firstOrFail();

        // Determine which credentials are being used
        $usingPrevious = $user->client_id !== $client_id;
        $secretToCheck = $usingPrevious ? $user->previous_client_secret : $user->client_secret;

        if (decrypt($secretToCheck) !== $client_secret) {
            throw new ModelNotFoundException('User not found');
        }

        return $this->createOAuthToken($user);
    }

    private function createOAuthToken(ApiUser $user): ApiToken
    {
        $user->tokens()
            ->where('expires_at', '<', Carbon::now())
            ->delete();

        return $user->tokens()->create([
            'token_type' => ApiToken::TYPE_OAUTH,
            'expires_at' => Carbon::now()->addSeconds(config('auth.api_users.oauth_token_expiry')),
            'token' => Str::random(42),
        ]);
    }

    private function createOAuthCredentials(ApiUser $user): void
    {
        $gracePeriodDays = config('auth.api_users.credential_grace_period', 7);

        // Store current credentials as previous (if they exist) for grace period
        $previousCredentials = [];
        if ($user->client_id) {
            $previousCredentials = [
                'previous_client_id' => $user->client_id,
                'previous_client_secret' => $user->client_secret,
                'previous_credentials_expires_at' => Carbon::now()->addDays($gracePeriodDays),
            ];
        }

        $user->update(array_merge($previousCredentials, [
            'credentials_last_updated_at' => Carbon::now(),
            'credentials_due_for_update_at' => Carbon::now()
                ->addDays(config('auth.api_users.credential_refresh_expiry')),
            'client_id' => Str::slug($user->name . Str::random(12)),
            'client_secret' => encrypt(Str::random(22)),
        ]));
    }

    private function createStaticToken(ApiUser $user): void
    {
        $user->tokens()->create([
            'token_type' => 'persistent',
            'expires_at' => Carbon::now()->addDays(config('auth.api_users.credential_refresh_expiry')),
            'token' => Str::random(42),
        ]);
    }

    /**
     * Clear expired previous credentials from all users.
     */
    public function clearExpiredPreviousCredentials(): int
    {
        return ApiUser::query()
            ->whereNotNull('previous_credentials_expires_at')
            ->where('previous_credentials_expires_at', '<', Carbon::now())
            ->update([
                'previous_client_id' => null,
                'previous_client_secret' => null,
                'previous_credentials_expires_at' => null,
            ]);
    }

    /**
     * Immediately revoke previous credentials for a specific user.
     */
    public function revokePreviousCredentials(ApiUser $user): void
    {
        $user->update([
            'previous_client_id' => null,
            'previous_client_secret' => null,
            'previous_credentials_expires_at' => null,
        ]);
    }
}
