<?php

namespace App\Http\Controllers\Dashboard;

use App\Http\Controllers\Controller;
use App\Models\AdminAuditLog;
use App\Models\Order;
use App\Models\Payment;
use App\Models\PricingChange;
use App\Models\PricingPlan;
use App\Models\Subscription;
use App\Models\User;
use App\Models\UserApiKey;
use App\Models\UserKeyword;
use App\Models\WebhookEvent;
use App\Services\Billing\PayPalPlanSyncService;
use App\Services\Keywords\KeywordQuotaService;
use App\Services\System\SettingsService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Schema;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;

class AdminDashboardController extends Controller
{
    public function __construct(
        private readonly SettingsService $settings,
        private readonly KeywordQuotaService $keywordQuota
    ) {}

    public function users(Request $request): View
    {
        $q = trim((string) $request->query('q', ''));
        $tier = trim((string) $request->query('tier', ''));
        $role = trim((string) $request->query('role', ''));
        $sort = $this->sanitizeSort($request->query('sort'), ['id', 'name', 'email', 'role', 'tier', 'created_at'], 'id');
        $dir = $this->sanitizeDir($request->query('dir'));

        $query = User::query();
        if ($q !== '') {
            $query->where(function ($sub) use ($q): void {
                $sub->where('email', 'like', '%'.$q.'%')
                    ->orWhere('name', 'like', '%'.$q.'%');
            });
        }
        if ($tier !== '') {
            $query->where('tier', $tier);
        }
        if ($role !== '') {
            $query->where('role', $role);
        }

        $users = $query->orderBy($sort, $dir)->paginate(20)->withQueryString();

        return view('dashboard.admin.users', [
            'users' => $users,
            'filters' => ['q' => $q, 'tier' => $tier, 'role' => $role],
            'sort' => $sort,
            'dir' => $dir,
        ]);
    }

    public function userDetail(User $user): View
    {
        $subscriptions = Subscription::where('user_id', $user->id)->orderByDesc('id')->get();

        return view('dashboard.admin.user-show', [
            'user' => $user,
            'subscriptions' => $subscriptions,
        ]);
    }

    public function updateUserTier(Request $request): RedirectResponse
    {
        $data = $request->validate([
            'user_id' => 'required|integer',
            'tier' => 'required|string|in:free,personal',
        ]);

        User::where('id', $data['user_id'])->update(['tier' => $data['tier']]);
        if ($data['tier'] === 'free') {
            UserApiKey::where('user_id', $data['user_id'])->update(['revoked_at' => now()]);
        }
        $this->keywordQuota->enforceForUser((int) $data['user_id'], (string) $data['tier']);
        $this->logAdminAction('user_tier_update', $data);

        return back()->with('status', 'User tier updated.');
    }

    public function createUser(Request $request): RedirectResponse
    {
        $data = $request->validate([
            'name' => 'nullable|string|max:120',
            'email' => 'required|email|max:255|unique:users,email',
            'password' => 'required|string|min:8|max:255',
            'role' => 'required|string|in:admin,user',
            'tier' => 'required|string|in:free,personal',
        ]);

        $name = $data['name'] ?: strtok($data['email'], '@');

        $user = User::create([
            'name' => $name ?: 'User',
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
            'role' => $data['role'],
            'tier' => $data['tier'],
        ]);

        $this->logAdminAction('user_created', [
            'user_id' => $user->id,
            'email' => $user->email,
            'role' => $user->role,
            'tier' => $user->tier,
        ]);

        return back()->with('status', 'User created.');
    }

    public function deleteUser(Request $request, User $user): RedirectResponse
    {
        $admin = $request->user();

        if ($admin && $admin->id === $user->id) {
            return back()->withErrors(['user' => 'You cannot delete your own account.']);
        }

        if (($user->role ?? 'user') === 'admin' && User::where('role', 'admin')->count() <= 1) {
            return back()->withErrors(['user' => 'Cannot delete the last admin account.']);
        }

        DB::transaction(function () use ($user): void {
            Payment::where('user_id', $user->id)->delete();
            Order::where('user_id', $user->id)->delete();
            Subscription::where('user_id', $user->id)->delete();
            UserApiKey::where('user_id', $user->id)->delete();
            UserKeyword::where('user_id', $user->id)->delete();
            DB::table('password_reset_tokens')->where('email', $user->email)->delete();
            $user->delete();
        });

        $this->logAdminAction('user_deleted', [
            'user_id' => $user->id,
            'email' => $user->email,
        ]);

        return back()->with('status', 'User deleted.');
    }

    public function subscriptions(Request $request): View
    {
        $query = Subscription::query()->with('user:id,email,name,tier');
        $sort = $this->sanitizeSort($request->query('sort'), ['id', 'plan', 'status', 'started_at', 'expires_at', 'created_at'], 'id');
        $dir = $this->sanitizeDir($request->query('dir'));

        if ($userId = $request->query('user_id')) {
            $query->where('user_id', (int) $userId);
        }
        if ($email = $request->query('email')) {
            $query->whereHas('user', fn ($q) => $q->where('email', $email));
        }
        if ($status = $request->query('status')) {
            $query->where('status', (string) $status);
        }

        $subscriptions = $query->orderBy($sort, $dir)->paginate(20)->withQueryString();

        return view('dashboard.admin.subscriptions', [
            'subscriptions' => $subscriptions,
            'filters' => [
                'user_id' => $request->query('user_id'),
                'email' => $request->query('email'),
                'status' => $request->query('status'),
            ],
            'sort' => $sort,
            'dir' => $dir,
        ]);
    }

    public function subscriptionDetail(Subscription $subscription): View
    {
        $subscription->loadMissing('user:id,name,email,tier,role');

        return view('dashboard.admin.subscription-show', [
            'subscription' => $subscription,
        ]);
    }

    public function grantSubscription(Request $request): RedirectResponse
    {
        $data = $request->validate([
            'user_id' => 'nullable|integer',
            'email' => 'nullable|email|max:255',
            'plan' => 'required|string|max:20',
            'status' => 'required|string|in:active,pending,revoked',
            'provider' => 'nullable|string|max:20',
            'provider_ref' => 'nullable|string|max:100',
            'started_at' => 'nullable|date',
            'expires_at' => 'nullable|date',
        ]);

        $user = $this->resolveUser($data['user_id'] ?? null, $data['email'] ?? null);
        if (! $user) {
            return back()->withErrors(['user' => 'User not found.']);
        }

        $startedAt = $data['started_at'] ? Carbon::parse($data['started_at']) : now();
        $expiresAt = $data['expires_at'] ? Carbon::parse($data['expires_at']) : null;

        Subscription::create([
            'user_id' => $user->id,
            'plan' => $data['plan'],
            'status' => $data['status'],
            'provider' => $data['provider'] ?? 'admin',
            'provider_ref' => $data['provider_ref'] ?? null,
            'started_at' => $startedAt,
            'expires_at' => $expiresAt,
        ]);

        if ($data['status'] === 'active') {
            $user->update(['tier' => 'personal']);
            $this->keywordQuota->enforceForUser((int) $user->id, 'personal');
        }

        $this->logAdminAction('subscription_grant', [
            'user_id' => $user->id,
            'email' => $user->email,
            'plan' => $data['plan'],
            'status' => $data['status'],
        ]);

        return back()->with('status', 'Subscription granted.');
    }

    public function revokeSubscription(Request $request): RedirectResponse
    {
        $data = $request->validate([
            'subscription_id' => 'nullable|integer',
            'user_id' => 'nullable|integer',
            'email' => 'nullable|email|max:255',
        ]);

        if (! empty($data['subscription_id'])) {
            $sub = Subscription::find($data['subscription_id']);
            if (! $sub) {
                return back()->withErrors(['subscription' => 'Subscription not found.']);
            }
            $sub->update(['status' => 'revoked', 'expires_at' => now()]);
            $this->syncUserTier($sub->user_id);
            $this->logAdminAction('subscription_revoke', [
                'subscription_id' => $sub->id,
                'user_id' => $sub->user_id,
            ]);

            return back()->with('status', 'Subscription revoked.');
        }

        $user = $this->resolveUser($data['user_id'] ?? null, $data['email'] ?? null);
        if (! $user) {
            return back()->withErrors(['user' => 'User not found.']);
        }

        Subscription::where('user_id', $user->id)
            ->where('status', 'active')
            ->update(['status' => 'revoked', 'expires_at' => now()]);
        $this->syncUserTier($user->id);
        $this->logAdminAction('subscription_revoke_all', [
            'user_id' => $user->id,
            'email' => $user->email,
        ]);

        return back()->with('status', 'User subscriptions revoked.');
    }

    public function pricing(): View
    {
        $plans = PricingPlan::orderBy('id')->get();
        $changes = PricingChange::orderByDesc('id')->limit(5)->get();

        return view('dashboard.admin.pricing', [
            'plans' => $plans,
            'changes' => $changes,
        ]);
    }

    public function updatePricing(Request $request): RedirectResponse
    {
        $data = $request->validate([
            'plans' => 'required|array|min:1',
            'plans.*.code' => 'required|string|max:30',
            'plans.*.name' => 'required|string|max:50',
            'plans.*.amount_idr' => 'required|integer|min:0',
            'plans.*.amount_usd' => 'nullable|numeric|min:0',
            'plans.*.period' => 'nullable|string|max:20',
            'plans.*.status' => 'nullable|string|max:20',
        ]);

        $before = PricingPlan::orderBy('id')->get()->toArray();

        DB::transaction(function () use ($data): void {
            foreach ($data['plans'] as $plan) {
                $meta = [
                    'prices' => [
                        'IDR' => (int) $plan['amount_idr'],
                        'USD' => isset($plan['amount_usd']) ? (float) $plan['amount_usd'] : null,
                    ],
                ];

                PricingPlan::updateOrCreate(
                    ['code' => $plan['code']],
                    [
                        'name' => $plan['name'],
                        'currency' => 'IDR',
                        'amount' => (int) $plan['amount_idr'],
                        'period' => $plan['period'] ?? null,
                        'status' => $plan['status'] ?? 'active',
                        'meta' => $meta,
                    ]
                );
            }
        });

        $after = PricingPlan::orderBy('id')->get()->toArray();
        PricingChange::create([
            'admin_ref' => (string) auth()->user()?->email,
            'before' => $before,
            'after' => $after,
        ]);
        $this->logAdminAction('pricing_update', ['plans' => count($data['plans'])]);

        return back()->with('status', 'Pricing updated.');
    }

    public function syncPaypalPlans(PayPalPlanSyncService $sync): RedirectResponse
    {
        try {
            $resultSandbox = $sync->sync('sandbox');
            $resultLive = $sync->sync('live');
        } catch (\Throwable $e) {
            try {
                $this->logAdminAction('paypal_plan_sync_failed', [
                    'error' => $e->getMessage(),
                ]);
            } catch (\Throwable) {
                // swallow secondary failures so sync endpoint never hard-crashes
            }

            return redirect()
                ->route('dashboard.admin.pricing')
                ->withErrors([
                    'pricing' => 'PayPal sync failed. Check server logs for details (network/credentials/proxy).',
                ]);
        }

        $this->logAdminAction('paypal_plan_sync', [
            'sandbox' => $resultSandbox,
            'live' => $resultLive,
        ]);

        return back()->with('status', 'PayPal plans synced (sandbox + live).');
    }

    public function createPricingSnapshot(): RedirectResponse
    {
        $before = PricingPlan::orderBy('id')->get()->toArray();

        PricingChange::create([
            'admin_ref' => (string) auth()->user()?->email,
            'before' => $before,
            'after' => $before,
        ]);
        $this->logAdminAction('pricing_snapshot', ['plans' => count($before)]);

        return back()->with('status', 'Pricing snapshot created.');
    }

    public function resetPricing(): RedirectResponse
    {
        $defaults = config('dewemoji.pricing.defaults', []);
        $before = PricingPlan::orderBy('id')->get()->toArray();

        DB::transaction(function () use ($defaults): void {
            PricingPlan::query()->delete();
            foreach ($defaults as $plan) {
                PricingPlan::create([
                    'code' => $plan['code'],
                    'name' => $plan['name'],
                    'currency' => $plan['currency'],
                    'amount' => $plan['amount'],
                    'period' => $plan['period'],
                    'status' => $plan['status'],
                    'meta' => $plan['meta'] ?? null,
                ]);
            }
        });

        $after = PricingPlan::orderBy('id')->get()->toArray();
        PricingChange::create([
            'admin_ref' => (string) auth()->user()?->email,
            'before' => $before,
            'after' => $after,
        ]);
        $this->logAdminAction('pricing_reset', ['plans' => count($after)]);

        return back()->with('status', 'Pricing reset to defaults.');
    }

    public function webhooks(Request $request): View
    {
        $query = WebhookEvent::query();
        $sort = $this->sanitizeSort($request->query('sort'), ['id', 'provider', 'status', 'received_at', 'created_at'], 'id');
        $dir = $this->sanitizeDir($request->query('dir'));
        if ($provider = $request->query('provider')) {
            $query->where('provider', (string) $provider);
        }
        if ($status = $request->query('status')) {
            $query->where('status', (string) $status);
        }

        $events = $query->orderBy($sort, $dir)->paginate(20)->withQueryString();
        $providers = WebhookEvent::query()->distinct('provider')->orderBy('provider')->pluck('provider');

        return view('dashboard.admin.webhooks', [
            'events' => $events,
            'filters' => [
                'provider' => $request->query('provider'),
                'status' => $request->query('status'),
            ],
            'providers' => $providers,
            'sort' => $sort,
            'dir' => $dir,
        ]);
    }

    public function webhookDetail(WebhookEvent $event): View
    {
        return view('dashboard.admin.webhook-show', [
            'event' => $event,
        ]);
    }

    public function replayWebhook(int $id): RedirectResponse
    {
        $event = WebhookEvent::find($id);
        if (! $event) {
            return back()->withErrors(['webhook' => 'Webhook not found.']);
        }

        $event->update(['status' => 'pending', 'processed_at' => null, 'error' => null]);
        $this->logAdminAction('webhook_replay', ['event_id' => $event->id]);

        return back()->with('status', 'Webhook queued for replay.');
    }

    public function replayFailedWebhooks(): RedirectResponse
    {
        $count = WebhookEvent::where('status', 'error')->count();
        WebhookEvent::where('status', 'error')->update([
            'status' => 'pending',
            'processed_at' => null,
            'error' => null,
        ]);
        $this->logAdminAction('webhook_replay_failed', ['count' => $count]);

        return back()->with('status', 'Failed webhooks queued for replay.');
    }

    public function settings(): View
    {
        $all = $this->settings->all();

        return view('dashboard.admin.settings', [
            'settings' => $all,
        ]);
    }

    public function auditLogs(Request $request): View
    {
        $q = trim((string) $request->query('q', ''));
        $action = trim((string) $request->query('action', ''));

        $query = AdminAuditLog::query()->orderByDesc('id');
        if ($q !== '') {
            $query->where(function ($sub) use ($q): void {
                $sub->where('admin_email', 'like', '%'.$q.'%')
                    ->orWhere('action', 'like', '%'.$q.'%');
            });
        }
        if ($action !== '') {
            $query->where('action', $action);
        }

        $actions = AdminAuditLog::query()->distinct('action')->orderBy('action')->pluck('action');
        $logs = $query->paginate(25)->withQueryString();

        return view('dashboard.admin.audit-logs', [
            'logs' => $logs,
            'filters' => ['q' => $q, 'action' => $action],
            'actions' => $actions,
        ]);
    }

    public function updateSettings(Request $request): RedirectResponse
    {
        $data = $request->validate([
            'maintenance_enabled' => 'nullable|boolean',
            'public_enforce' => 'nullable|boolean',
            'public_origins' => 'nullable|string',
            'public_extension_ids' => 'nullable|string',
            'public_hourly_limit' => 'nullable|integer|min:0',
            'billing_mode' => 'nullable|string|in:sandbox,live',
        ]);

        $payload = [
            'maintenance_enabled' => (bool) ($data['maintenance_enabled'] ?? false),
            'public_enforce' => (bool) ($data['public_enforce'] ?? false),
            'public_origins' => $this->splitCsv($data['public_origins'] ?? ''),
            'public_extension_ids' => $this->splitCsv($data['public_extension_ids'] ?? ''),
            'public_hourly_limit' => (int) ($data['public_hourly_limit'] ?? 0),
            'billing_mode' => $data['billing_mode'] ?? null,
        ];

        $this->settings->setMany($payload, (string) auth()->user()?->email);
        $this->logAdminAction('settings_update', $payload);

        return back()->with('status', 'Settings updated.');
    }

    private function resolveUser(?int $userId, ?string $email): ?User
    {
        if ($userId) {
            return User::find($userId);
        }
        if ($email) {
            return User::where('email', $email)->first();
        }

        return null;
    }

    private function syncUserTier(int $userId): void
    {
        $active = Subscription::where('user_id', $userId)
            ->where('status', 'active')
            ->where(function ($q): void {
                $q->whereNull('expires_at')
                    ->orWhere('expires_at', '>', now());
            })
            ->exists();

        User::where('id', $userId)->update([
            'tier' => $active ? 'personal' : 'free',
        ]);
        $this->keywordQuota->enforceForUser($userId, $active ? 'personal' : 'free');
        if (! $active) {
            UserApiKey::where('user_id', $userId)->update(['revoked_at' => now()]);
        }
    }

    public function exportCsv(Request $request, string $type): StreamedResponse
    {
        $type = strtolower($type);
        $filename = "dewemoji-{$type}-export-".now()->format('Ymd_His').'.csv';

        return response()->streamDownload(function () use ($type, $request): void {
            $out = fopen('php://output', 'w');

            if ($type === 'users') {
                $q = trim((string) $request->query('q', ''));
                $tier = trim((string) $request->query('tier', ''));
                $role = trim((string) $request->query('role', ''));
                $sort = $this->sanitizeSort($request->query('sort'), ['id', 'name', 'email', 'role', 'tier', 'created_at'], 'id');
                $dir = $this->sanitizeDir($request->query('dir'));

                $query = User::query();
                if ($q !== '') {
                    $query->where(function ($sub) use ($q): void {
                        $sub->where('email', 'like', '%'.$q.'%')
                            ->orWhere('name', 'like', '%'.$q.'%');
                    });
                }
                if ($tier !== '') {
                    $query->where('tier', $tier);
                }
                if ($role !== '') {
                    $query->where('role', $role);
                }

                fputcsv($out, ['id', 'name', 'email', 'role', 'tier', 'created_at']);
                $query->orderBy($sort, $dir)->chunk(500, function ($rows) use ($out): void {
                    foreach ($rows as $row) {
                        fputcsv($out, [
                            $row->id,
                            $row->name,
                            $row->email,
                            $row->role,
                            $row->tier,
                            optional($row->created_at)->toDateTimeString(),
                        ]);
                    }
                });
            } elseif ($type === 'subscriptions') {
                $userId = $request->query('user_id');
                $email = $request->query('email');
                $status = $request->query('status');
                $sort = $this->sanitizeSort($request->query('sort'), ['id', 'plan', 'status', 'started_at', 'expires_at', 'created_at'], 'id');
                $dir = $this->sanitizeDir($request->query('dir'));

                $query = Subscription::query()->with('user:id,email');
                if ($userId) {
                    $query->where('user_id', (int) $userId);
                }
                if ($email) {
                    $query->whereHas('user', fn ($q) => $q->where('email', $email));
                }
                if ($status) {
                    $query->where('status', (string) $status);
                }

                fputcsv($out, ['id', 'user_id', 'email', 'plan', 'status', 'started_at', 'expires_at']);
                $query->orderBy($sort, $dir)->chunk(500, function ($rows) use ($out): void {
                    foreach ($rows as $row) {
                        fputcsv($out, [
                            $row->id,
                            $row->user_id,
                            $row->user?->email,
                            $row->plan,
                            $row->status,
                            optional($row->started_at)->toDateTimeString(),
                            optional($row->expires_at)->toDateTimeString(),
                        ]);
                    }
                });
            } elseif ($type === 'webhooks') {
                $provider = $request->query('provider');
                $status = $request->query('status');
                $sort = $this->sanitizeSort($request->query('sort'), ['id', 'provider', 'status', 'received_at', 'created_at'], 'id');
                $dir = $this->sanitizeDir($request->query('dir'));

                $query = WebhookEvent::query();
                if ($provider) {
                    $query->where('provider', (string) $provider);
                }
                if ($status) {
                    $query->where('status', (string) $status);
                }

                fputcsv($out, ['id', 'provider', 'event_type', 'status', 'received_at', 'processed_at']);
                $query->orderBy($sort, $dir)->chunk(500, function ($rows) use ($out): void {
                    foreach ($rows as $row) {
                        fputcsv($out, [
                            $row->id,
                            $row->provider,
                            $row->event_type,
                            $row->status,
                            optional($row->received_at)->toDateTimeString(),
                            optional($row->processed_at)->toDateTimeString(),
                        ]);
                    }
                });
            } else {
                fputcsv($out, ['error']);
                fputcsv($out, ['unsupported_export_type']);
            }

            fclose($out);
        }, $filename, [
            'Content-Type' => 'text/csv; charset=UTF-8',
        ]);
    }

    private function logAdminAction(string $action, array $payload = []): void
    {
        if (! Schema::hasTable('admin_audit_logs')) {
            return;
        }
        $user = auth()->user();
        AdminAuditLog::create([
            'admin_id' => $user?->id,
            'admin_email' => $user?->email,
            'action' => $action,
            'payload' => $payload,
            'ip_address' => request()->ip(),
        ]);
    }

    private function sanitizeSort(mixed $value, array $allowed, string $fallback): string
    {
        $sort = is_string($value) ? $value : '';

        return in_array($sort, $allowed, true) ? $sort : $fallback;
    }

    private function sanitizeDir(mixed $value): string
    {
        return $value === 'asc' ? 'asc' : 'desc';
    }

    /**
     * @return array<int,string>
     */
    private function splitCsv(string $value): array
    {
        $items = array_filter(array_map('trim', explode(',', $value)));

        return array_values($items);
    }
}
