<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\ImpersonationToken;
use App\Models\Tenant;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;

/**
 * SaaS-admin impersonation flow (ТЗ §22.7 / Ю-1).
 *
 * Flow:
 *   1. Admin → POST /api/admin/impersonation/init {tenant_id, reason}.
 *      Backend генерирует 6-значный код, сохраняет hash в impersonation_tokens,
 *      отправляет plain код на tenant.contact_email. TTL 15 минут.
 *   2. Admin вводит код в админке → POST /api/admin/impersonation/verify {token_id, code}.
 *      Backend проверяет hash, expiry, attempts. На success — token.used_at = NOW(),
 *      возвращает session_id (создаётся отдельно — отдельный коммит).
 *   3. Admin завершает сессию → POST /api/admin/impersonation/end {token_id}.
 *      Backend ставит token.session_ended_at, отправляет email клиенту.
 *
 * NB: на MVP saas_admin_users auth не реализован, `requested_by` принимается
 * параметром. Production: middleware('auth:saas-admin') + auth()->id().
 *
 * Two-person approval (CTO-15 / Ю-9): для тенантов с
 * `pd_subject_request.processing_restricted=TRUE` ИЛИ
 * `chargeback_unrecovered_rub > 0` требуется second_approver_id роли compliance.
 * На MVP не реализовано — отдельный коммит после SaaS-admin auth.
 */
class ImpersonationController extends Controller
{
    private const TOKEN_TTL_MINUTES = 15;

    private const MAX_FAILED_ATTEMPTS = 5;

    /** GET /api/admin/impersonation/active — активные сессии (used_at != null AND session_ended_at == null) */
    public function active(): JsonResponse
    {
        $rows = ImpersonationToken::query()
            ->whereNotNull('used_at')
            ->whereNull('session_ended_at')
            ->with(['tenant'])
            ->orderByDesc('used_at')
            ->limit(100)
            ->get(['id', 'tenant_id', 'requested_by', 'reason', 'sent_to_email', 'used_at', 'expires_at']);

        return response()->json([
            'sessions' => $rows->map(fn (ImpersonationToken $t) => [
                'token_id' => $t->id,
                'tenant_id' => $t->tenant_id,
                'tenant_name' => $t->tenant?->organization_name,
                'requested_by' => $t->requested_by,
                'reason' => $t->reason,
                'sent_to_email' => $t->sent_to_email,
                'used_at' => $t->used_at?->toIso8601String(),
                'expires_at' => $t->expires_at->toIso8601String(),
            ]),
        ]);
    }

    /** GET /api/admin/impersonation/recent — последние 20 завершённых */
    public function recent(): JsonResponse
    {
        $rows = ImpersonationToken::query()
            ->whereNotNull('used_at')
            ->whereNotNull('session_ended_at')
            ->with(['tenant'])
            ->orderByDesc('session_ended_at')
            ->limit(20)
            ->get(['id', 'tenant_id', 'requested_by', 'reason', 'used_at', 'session_ended_at']);

        return response()->json([
            'sessions' => $rows->map(fn (ImpersonationToken $t) => [
                'token_id' => $t->id,
                'tenant_id' => $t->tenant_id,
                'tenant_name' => $t->tenant?->organization_name,
                'requested_by' => $t->requested_by,
                'reason' => $t->reason,
                'used_at' => $t->used_at?->toIso8601String(),
                'session_ended_at' => $t->session_ended_at?->toIso8601String(),
                'duration_seconds' => $t->used_at && $t->session_ended_at
                    ? abs($t->session_ended_at->diffInSeconds($t->used_at))
                    : null,
            ]),
        ]);
    }

    /** POST /api/admin/impersonation/init */
    public function init(Request $request): JsonResponse
    {
        $tenantId = (int) $request->input('tenant_id');
        $requestedBy = (int) $request->input('requested_by'); // TODO: $request->user()->id когда saas-admin auth готов
        $reason = $request->string('reason')->toString();

        if (mb_strlen($reason) < 30) {
            return response()->json([
                'message' => 'Основание (reason) должно быть не короче 30 символов.',
                'errors' => ['reason' => ['Минимум 30 символов.']],
            ], 422);
        }

        $tenant = Tenant::find($tenantId);
        if (! $tenant) {
            return response()->json(['message' => 'Тенант не найден.'], 404);
        }

        // 6-значный код. Числа от 100000 до 999999.
        $plainCode = (string) random_int(100_000, 999_999);

        $token = ImpersonationToken::create([
            'tenant_id' => $tenant->id,
            'requested_by' => $requestedBy,
            'code_hash' => Hash::make($plainCode),
            'reason' => $reason,
            'sent_to_email' => (string) $tenant->contact_email,
            'expires_at' => now()->addMinutes(self::TOKEN_TTL_MINUTES),
        ]);

        // TODO: отправить email на $tenant->contact_email с $plainCode.
        // На MVP возвращаем code в response для тестов / dev (на prod НЕ должно
        // возвращаться никогда — токен только в email клиента).
        return response()->json([
            'token_id' => $token->id,
            'expires_at' => $token->expires_at->toIso8601String(),
            'sent_to_email' => $token->sent_to_email,
            // dev-only field — на prod исчезает после интеграции с MailService.
            '_dev_plain_code' => $plainCode,
        ]);
    }

    /** POST /api/admin/impersonation/verify */
    public function verify(Request $request): JsonResponse
    {
        $tokenId = (int) $request->input('token_id');
        $code = $request->string('code')->toString();

        $token = ImpersonationToken::find($tokenId);
        if (! $token) {
            return response()->json(['message' => 'Токен не найден.'], 404);
        }

        if (! $token->isUsable()) {
            $reason = $token->isExpired() ? 'expired'
                : ($token->used_at !== null ? 'used'
                : ($token->invalidated_at !== null ? 'invalidated'
                : 'too_many_attempts'));

            return response()->json([
                'message' => 'Токен недействителен.',
                'reason' => $reason,
            ], 422);
        }

        if (! Hash::check($code, $token->code_hash)) {
            DB::transaction(function () use ($token) {
                $token->increment('failed_attempts');
                if ($token->failed_attempts >= self::MAX_FAILED_ATTEMPTS) {
                    $token->update(['invalidated_at' => now()]);
                }
            });

            return response()->json([
                'message' => 'Неверный код.',
                'attempts_remaining' => max(0, self::MAX_FAILED_ATTEMPTS - $token->failed_attempts),
            ], 422);
        }

        // Success: mark used. Создание saas_admin_session с
        // impersonating_token_id — отдельный коммит после saas-admin auth.
        $token->update([
            'used_at' => now(),
        ]);

        return response()->json([
            'token_id' => $token->id,
            'tenant_id' => $token->tenant_id,
            'used_at' => $token->used_at->toIso8601String(),
            'message' => 'Impersonation начат. Сессия активна 1 час.',
        ]);
    }

    /** POST /api/admin/impersonation/end */
    public function end(Request $request): JsonResponse
    {
        $tokenId = (int) $request->input('token_id');

        $token = ImpersonationToken::find($tokenId);
        if (! $token) {
            return response()->json(['message' => 'Токен не найден.'], 404);
        }

        if ($token->used_at === null) {
            return response()->json([
                'message' => 'Сессия impersonation не была активирована.',
            ], 422);
        }

        if ($token->session_ended_at !== null) {
            return response()->json([
                'message' => 'Сессия уже завершена.',
            ], 422);
        }

        $token->update(['session_ended_at' => now()]);

        // TODO: уведомление клиенту по email о завершении (как и в init flow).

        return response()->json([
            'token_id' => $token->id,
            'session_ended_at' => $token->session_ended_at->toIso8601String(),
            'message' => 'Impersonation завершён.',
        ]);
    }
}
