<?php

namespace App\Http\Controllers\Earn;

use App\Enums\EarnApiTokenStatus;
use App\Http\Controllers\Controller;
use App\Models\EarnApiToken;
use App\Models\EarnMember;
use App\Services\EarnAuditLogger;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

class EarnApiKeyController extends Controller
{
    public function index(Request $request): Response
    {
        $member = $this->member($request);
        $tokens = $member->tokens()->latest()->get();

        return response()->view('earn-panel.api-keys', [
            'tokens' => $tokens,
            'maxTotal' => (int) config('earn.api_keys.max_total'),
            'durations' => config('earn.api_keys.allowed_durations'),
            'defaultDuration' => config('earn.api_keys.default_duration'),
            'tasksUrl' => route('test-tasks.index'),
        ])->withHeaders(['Cache-Control' => 'no-store, private', 'Pragma' => 'no-cache']);
    }

    public function store(Request $request, EarnAuditLogger $audit): RedirectResponse
    {
        $request->merge(['name' => trim(strip_tags((string) $request->input('name')))]);
        $data = $request->validate([
            'name' => ['required', 'string', 'min:3', 'max:80'],
            'duration' => ['required', 'string', Rule::in(config('earn.api_keys.allowed_durations'))],
        ]);
        $member = $this->member($request);
        $result = DB::transaction(function () use ($member, $data, $request, $audit): array {
            EarnMember::query()->whereKey($member->id)->lockForUpdate()->firstOrFail();
            if ($member->tokens()->count() >= (int) config('earn.api_keys.max_total')) {
                return ['error' => 'total'];
            }

            $plainText = 'tpe_'.rtrim(strtr(base64_encode(random_bytes(24)), '+/', '-_'), '=');
            $expiresAt = $this->expiresAt($data['duration']);
            $token = $member->tokens()->create([
                'name' => $data['name'], 'token' => hash('sha256', $plainText), 'token_hint' => 'tpe_'.substr($plainText, -4),
                'abilities' => ['tasks:read'], 'is_active' => true, 'expires_at' => $expiresAt,
            ]);
            $audit->record('api_key_created', $member, $request, $this->metadata($token));

            return ['token' => $plainText];
        }, 3);

        if (isset($result['error'])) {
            $audit->record('api_key_limit_rejected', $member, $request, ['device_name' => $data['name'], 'abilities' => ['tasks:read'], 'limit' => $result['error']]);
            throw ValidationException::withMessages(['api_key' => __('earn_portal.total_limit_reached')]);
        }

        return redirect()->route('earn.api-keys.index')->with('new_api_key', $result['token']);
    }

    public function status(Request $request, int $apiKey, EarnAuditLogger $audit): RedirectResponse
    {
        $data = $request->validate(['is_active' => ['required', 'boolean']]);
        $member = $this->member($request);
        DB::transaction(function () use ($member, $apiKey, $data, $request, $audit): void {
            EarnMember::query()->whereKey($member->id)->lockForUpdate()->firstOrFail();
            $token = $member->tokens()->whereKey($apiKey)->lockForUpdate()->firstOrFail();
            abort_if(in_array($token->status(), [EarnApiTokenStatus::Expired, EarnApiTokenStatus::Revoked], true), 422);
            $activate = (bool) $data['is_active'];
            if ($token->is_active === $activate) {
                return;
            }
            $token->forceFill(['is_active' => $activate])->save();
            $audit->record($activate ? 'api_key_activated' : 'api_key_deactivated', $member, $request, $this->metadata($token));
        }, 3);

        return back()->with('status', __('earn_portal.status_updated'));
    }

    public function revoke(Request $request, int $apiKey, EarnAuditLogger $audit): RedirectResponse
    {
        $member = $this->member($request);
        DB::transaction(function () use ($member, $apiKey, $request, $audit): void {
            $token = $member->tokens()->whereKey($apiKey)->lockForUpdate()->firstOrFail();
            abort_if($token->revoked_at !== null, 404);
            $token->forceFill(['revoked_at' => now(), 'is_active' => false])->save();
            $audit->record('api_key_revoked', $member, $request, $this->metadata($token));
        }, 3);

        return back()->with('status', __('earn_portal.key_revoked'));
    }

    public function destroy(Request $request, int $apiKey, EarnAuditLogger $audit): RedirectResponse
    {
        $member = $this->member($request);
        DB::transaction(function () use ($member, $apiKey, $request, $audit): void {
            $token = $member->tokens()->whereKey($apiKey)->lockForUpdate()->firstOrFail();
            $token->forceFill(['revoked_at' => $token->revoked_at ?? now(), 'is_active' => false])->save();
            $metadata = $this->metadata($token) + ['deleted_at' => now()->toIso8601String(), 'actor_earn_member_id' => $member->id];
            $audit->record('api_key_deleted', $member, $request, $metadata);
            $token->delete();
        }, 3);

        return back()->with('status', __('earn_portal.key_deleted'));
    }

    private function expiresAt(string $duration): ?Carbon
    {
        return match ($duration) {
            '1_day' => now()->addDay(), '7_days' => now()->addDays(7), '30_days' => now()->addDays(30),
            '90_days' => now()->addDays(90), '180_days' => now()->addDays(180), '1_year' => now()->addYear(),
            'unlimited' => null,
        };
    }

    private function metadata(EarnApiToken $token): array
    {
        return ['token_id' => $token->id, 'device_name' => $token->name, 'abilities' => $token->abilities,
            'created_at' => $token->created_at?->toIso8601String(), 'expires_at' => $token->expires_at?->toIso8601String(),
            'revoked_at' => $token->revoked_at?->toIso8601String()];
    }

    private function member(Request $request): EarnMember
    {
        $member = $request->user('earn');
        abort_unless($member instanceof EarnMember, 403);

        return $member;
    }
}
