<?php

namespace App\Services\Care;

use App\Models\Organization;
use App\Models\Patient;
use App\Models\User;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

/**
 * Reseed Care demo tenants after a fresh SSO login.
 *
 * Only owner demo accounts (and legacy enterprise org members) trigger a full
 * reseed. Staff training logins never wipe mid-demo.
 *
 * The onboarded org shell is created synchronously before the login redirect so
 * EnsureOrganizationSetup never flashes the blank onboarding form. Heavy demo
 * data still refreshes afterResponse() when the tenant already has patients;
 * empty tenants (failed prior reseed / onboarding race) seed synchronously.
 */
class DemoLoginReseeder
{
    public function maybeResetAfterLogin(User $user): void
    {
        if (! (bool) config('demo_accounts.enabled', false)
            || ! (bool) config('demo_accounts.reset_on_login', true)) {
            return;
        }

        // Staff demos share the owner tenant but must not reseed on login.
        if (\App\Support\DemoWorld::isStaffDemoEmail($user->email)) {
            return;
        }

        $plan = $this->planForEmail($user->email);
        if ($plan === null) {
            return;
        }

        $seedUser = $this->resolveSeedUser($user);
        if (! $seedUser) {
            return;
        }

        $lockKey = 'demo-reseed:care:'.$seedUser->public_id;
        if (! Cache::add($lockKey, 1, 120)) {
            // Another login is already reseeding — still guarantee the shell exists.
            $this->tryShell($seedUser, $plan);

            return;
        }

        $organization = $this->tryShell($seedUser, $plan);
        if ($organization === null) {
            Cache::forget($lockKey);

            return;
        }

        $empty = Patient::query()->where('organization_id', $organization->id)->doesntExist();
        if ($empty) {
            // Recover immediately so sales never land on an empty dashboard after
            // a prior lock-timeout / onboarding race wiped or skipped demo data.
            try {
                $this->withLockRetry(fn () => app(DemoTenantSeeder::class)->seed($seedUser, $plan, reset: true));
            } catch (\Throwable $e) {
                Log::warning('demo_care_login_reseed_failed', [
                    'user_id' => $seedUser->id,
                    'plan' => $plan,
                    'error' => $e->getMessage(),
                ]);
            } finally {
                Cache::forget($lockKey);
            }

            return;
        }

        $userId = $seedUser->id;
        dispatch(function () use ($userId, $plan, $lockKey) {
            try {
                $user = User::query()->find($userId);
                if ($user) {
                    $this->withLockRetry(fn () => app(DemoTenantSeeder::class)->seed($user, $plan, reset: true));
                }
            } catch (\Throwable $e) {
                Log::warning('demo_care_login_reseed_failed', [
                    'user_id' => $userId,
                    'plan' => $plan,
                    'error' => $e->getMessage(),
                ]);
            } finally {
                Cache::forget($lockKey);
            }
        })->afterResponse();
    }

    private function tryShell(User $seedUser, string $plan): ?Organization
    {
        try {
            return $this->withLockRetry(
                fn () => app(DemoTenantSeeder::class)->ensureOnboardedShell($seedUser, $plan)
            );
        } catch (\Throwable $e) {
            Log::warning('demo_care_login_shell_failed', [
                'user_id' => $seedUser->id,
                'plan' => $plan,
                'error' => $e->getMessage(),
            ]);

            return null;
        }
    }

    /**
     * @template T
     *
     * @param  callable(): T  $callback
     * @return T
     */
    private function withLockRetry(callable $callback): mixed
    {
        return retry(4, $callback, 150, function (\Throwable $e) {
            return $e instanceof QueryException && $this->isLockWait($e);
        });
    }

    private function isLockWait(QueryException $e): bool
    {
        $message = $e->getMessage();

        return str_contains($message, '1205')
            || str_contains($message, 'Lock wait timeout')
            || str_contains($message, '1213')
            || str_contains($message, 'Deadlock');
    }

    private function planForEmail(?string $email): ?string
    {
        $email = strtolower(trim((string) $email));
        if ($email === '') {
            return null;
        }

        $accounts = (array) config('demo_accounts.accounts', []);
        if (isset($accounts[$email])) {
            return $accounts[$email];
        }

        $members = (array) config('demo_accounts.member_emails', []);
        $ownerEmail = $members[$email] ?? null;

        return $ownerEmail ? ($accounts[$ownerEmail] ?? null) : null;
    }

    private function resolveSeedUser(User $user): ?User
    {
        $email = strtolower((string) $user->email);
        $ownerEmail = ((array) config('demo_accounts.member_emails', []))[$email] ?? null;
        if (! $ownerEmail) {
            return $user;
        }

        return User::query()->where('email', $ownerEmail)->first() ?? $user;
    }
}
