<?php

namespace App\Services;

use App\Models\MexcoCampusApplication;
use App\Models\MexcoEventLicense;
use App\Models\MexcoOrganisation;
use App\Models\MexcoOrganisationUser;
use App\Models\MexcoPlan;
use App\Models\MeetupEvent;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class CampusService
{
    public function __construct(
        private MeetupService $meetup,
        private EntitlementService $entitlements,
        private OrganisationService $organisations,
        private UsageService $usage,
        private NotificationService $notifications,
        private SettingsService $settings,
    ) {}

    public function createApplication(array $data): MexcoCampusApplication
    {
        return MexcoCampusApplication::query()->create([
            'reference_no' => $this->generateReferenceNo(),
            'organisation_id' => $data['organisation_id'] ?? null,
            'organisation_user_id' => $data['organisation_user_id'] ?? null,
            'institution' => $data['institution'],
            'faculty' => $data['faculty'] ?? null,
            'student_organisation' => $data['student_organisation'] ?? null,
            'event_name' => $data['event_name'],
            'event_date' => $data['event_date'] ?? null,
            'expected_participants' => $data['expected_participants'] ?? null,
            'sessions_count' => $data['sessions_count'] ?? null,
            'booths_count' => $data['booths_count'] ?? null,
            'event_objective' => $data['event_objective'] ?? null,
            'event_description' => $data['event_description'] ?? null,
            'contact_person' => $data['contact_person'],
            'contact_email' => strtolower(trim($data['contact_email'])),
            'applicant_phone' => $data['applicant_phone'] ?? null,
            'student_id' => $data['student_id'] ?? null,
            'advisor_name' => $data['advisor_name'] ?? null,
            'advisor_email' => $data['advisor_email'] ?? null,
            'requested_sponsorship' => $data['requested_sponsorship'] ?? null,
            'promotional_exposure' => $data['promotional_exposure'] ?? null,
            'sponsorship_letter_path' => $data['sponsorship_letter_path'] ?? null,
            'sponsorship_letter_name' => $data['sponsorship_letter_name'] ?? null,
            'sponsorship_value_cents' => $data['sponsorship_value_cents']
                ?? $this->settings->get('campus', 'sponsorship_value_cents', config('mexco.campus.default_sponsorship_value_cents')),
            'status' => MexcoCampusApplication::STATUS_SUBMITTED,
        ]);
    }

    public function submitForOrganisationUser(MexcoOrganisationUser $user, array $data): MexcoCampusApplication
    {
        $organisation = $user->organisation;
        if (! $organisation) {
            throw ValidationException::withMessages([
                'organisation' => 'Your organisation account is missing.',
            ]);
        }

        $pending = $this->pendingForOrganisation((int) $organisation->id);
        if ($pending) {
            return $pending;
        }

        return $this->createApplication([
            'organisation_id' => $organisation->id,
            'organisation_user_id' => $user->id,
            'institution' => $data['institution'] ?? $organisation->name,
            'faculty' => $data['faculty'] ?? null,
            'student_organisation' => $data['student_organisation'] ?? null,
            'event_name' => $data['event_name'],
            'event_date' => $data['event_date'] ?? null,
            'expected_participants' => $data['expected_participants'] ?? null,
            'sessions_count' => $data['sessions_count'] ?? null,
            'booths_count' => $data['booths_count'] ?? null,
            'event_objective' => $data['event_objective'] ?? null,
            'requested_sponsorship' => $data['requested_sponsorship'] ?? null,
            'promotional_exposure' => $data['promotional_exposure'] ?? null,
            'contact_person' => $data['contact_person'] ?? $user->name,
            'contact_email' => $data['contact_email'] ?? $user->email,
            'applicant_phone' => $data['applicant_phone'] ?? $user->phone,
            'sponsorship_letter_path' => $data['sponsorship_letter_path'] ?? null,
            'sponsorship_letter_name' => $data['sponsorship_letter_name'] ?? null,
        ]);
    }

    public function activateApplicantAccess(MexcoCampusApplication $application): void
    {
        if ($application->organisation_id) {
            MexcoOrganisation::query()
                ->where('id', $application->organisation_id)
                ->whereIn('status', ['pending', 'rejected'])
                ->update(['status' => 'active']);

            MexcoOrganisationUser::query()
                ->where('organisation_id', $application->organisation_id)
                ->whereIn('status', ['pending', 'rejected'])
                ->update(['status' => 'active']);
        }

        if ($application->organisation_user_id) {
            MexcoOrganisationUser::query()
                ->where('id', $application->organisation_user_id)
                ->whereIn('status', ['pending', 'rejected'])
                ->update(['status' => 'active']);
        }
    }

    public function revokeApplicantAccess(MexcoCampusApplication $application): void
    {
        if ($application->organisation_id) {
            MexcoOrganisation::query()
                ->where('id', $application->organisation_id)
                ->where('status', 'pending')
                ->update(['status' => 'rejected']);

            MexcoOrganisationUser::query()
                ->where('organisation_id', $application->organisation_id)
                ->where('status', 'pending')
                ->update(['status' => 'rejected']);
        }
    }

    public function pendingForOrganisation(int $organisationId): ?MexcoCampusApplication
    {
        return MexcoCampusApplication::query()
            ->where('organisation_id', $organisationId)
            ->whereIn('status', [
                MexcoCampusApplication::STATUS_SUBMITTED,
                MexcoCampusApplication::STATUS_UNDER_REVIEW,
            ])
            ->orderByDesc('created_at')
            ->first();
    }

    public function latestForOrganisation(int $organisationId): ?MexcoCampusApplication
    {
        return MexcoCampusApplication::query()
            ->where('organisation_id', $organisationId)
            ->orderByDesc('created_at')
            ->first();
    }

    public function markUnderReview(MexcoCampusApplication $application, ?int $reviewerId = null): MexcoCampusApplication
    {
        $this->assertStatus($application, [
            MexcoCampusApplication::STATUS_SUBMITTED,
            MexcoCampusApplication::STATUS_UNDER_REVIEW,
        ]);

        $application->update([
            'status' => MexcoCampusApplication::STATUS_UNDER_REVIEW,
            'reviewed_by' => $reviewerId,
        ]);

        return $application->fresh();
    }

    public function reject(MexcoCampusApplication $application, string $notes, ?int $reviewerId = null): MexcoCampusApplication
    {
        $this->assertStatus($application, [
            MexcoCampusApplication::STATUS_SUBMITTED,
            MexcoCampusApplication::STATUS_UNDER_REVIEW,
        ]);

        $application->update([
            'status' => MexcoCampusApplication::STATUS_REJECTED,
            'review_notes' => $notes,
            'reviewed_by' => $reviewerId,
            'reviewed_at' => now(),
        ]);

        $this->revokeApplicantAccess($application);

        return $application->fresh();
    }

    public function approve(MexcoCampusApplication $application, array $options = [], ?int $reviewerId = null): MexcoCampusApplication
    {
        $this->assertStatus($application, [
            MexcoCampusApplication::STATUS_SUBMITTED,
            MexcoCampusApplication::STATUS_UNDER_REVIEW,
            MexcoCampusApplication::STATUS_APPROVED,
        ]);

        if ($application->event_id) {
            throw ValidationException::withMessages([
                'application' => 'This application already has an event.',
            ]);
        }

        return DB::transaction(function () use ($application, $options, $reviewerId) {
            $organisation = $this->resolveOrganisation($application, $options);
            $sponsorshipValue = $options['sponsorship_value_cents']
                ?? $application->sponsorship_value_cents
                ?? $this->settings->get('campus', 'sponsorship_value_cents', config('mexco.campus.default_sponsorship_value_cents'));

            [$startsAt, $endsAt] = $this->eventWindow($application, $options);
            $organiserPassword = $options['organiser_password'] ?? Str::random(10);

            $event = $this->meetup->createEvent([
                'id' => $options['event_id'] ?? null,
                'organisation_id' => $organisation->id,
                'name' => $application->event_name,
                'venue' => $options['venue'] ?? $application->institution,
                'dates' => $application->event_date?->format('d M Y') ?? '',
                'event_name' => $application->event_name,
                'organiser_username' => $options['organiser_email'] ?? $application->contact_email,
                'organiser_password' => $organiserPassword,
                'allow_voting' => true,
                'allow_floor_plan' => true,
                'starts_at' => $startsAt,
                'ends_at' => $endsAt,
            ]);

            $license = $this->assignCampusLicense($event, $sponsorshipValue);
            $this->usage->recalculate($event->id);
            $this->entitlements->syncLegacyFlags($event->fresh());
            $this->organisations->migrateLegacyOrganiser($event->fresh());
            $this->activateApplicantAccess($application);

            $application->update([
                'organisation_id' => $organisation->id,
                'status' => MexcoCampusApplication::STATUS_EVENT_CREATED,
                'sponsorship_value_cents' => $sponsorshipValue,
                'reviewed_by' => $reviewerId,
                'reviewed_at' => now(),
                'review_notes' => $options['review_notes'] ?? $application->review_notes,
                'event_id' => $event->id,
                'event_license_id' => $license->id,
                'metadata' => array_merge($application->metadata ?? [], [
                    'organiser_password_hint' => $options['organiser_password'] ? null : 'auto-generated',
                ]),
            ]);

            Log::info('Campus application approved', [
                'reference' => $application->reference_no,
                'event_id' => $event->id,
            ]);

            return $application->fresh(['event', 'license', 'organisation']);
        });
    }

    public function extendRetention(MexcoEventLicense $license, int $extraDays): MexcoEventLicense
    {
        if (! $license->is_campus_sponsored || ! $license->retention_expires_at) {
            throw ValidationException::withMessages([
                'license' => 'Only campus sponsored events in retention can be extended.',
            ]);
        }

        $license->update([
            'retention_expires_at' => $license->retention_expires_at->addDays($extraDays),
            'retention_warned_at' => null,
        ]);

        MexcoCampusApplication::query()
            ->where('event_license_id', $license->id)
            ->update(['status' => MexcoCampusApplication::STATUS_RETENTION]);

        return $license->fresh();
    }

    public function syncCampusEventStatuses(): int
    {
        $updated = 0;
        $retentionDays = (int) $this->settings->get('campus', 'retention_days', config('mexco.campus.default_retention_days', 30));

        $applications = MexcoCampusApplication::query()
            ->with(['event', 'license'])
            ->whereNotNull('event_id')
            ->whereNotIn('status', [
                MexcoCampusApplication::STATUS_REJECTED,
                MexcoCampusApplication::STATUS_DELETED,
            ])
            ->get();

        foreach ($applications as $application) {
            $event = $application->event;
            $license = $application->license;

            if (! $event || ! $license) {
                continue;
            }

            $availability = $this->meetup->eventAvailability($event);
            $newStatus = $application->status;

            if (in_array($availability, ['live', 'open'], true)) {
                $newStatus = MexcoCampusApplication::STATUS_ACTIVE;
            } elseif ($availability === 'upcoming') {
                $newStatus = MexcoCampusApplication::STATUS_EVENT_CREATED;
            } elseif ($availability === 'ended') {
                if (! $license->retention_expires_at) {
                    $endsAt = $event->ends_at ?? now();
                    $license->update([
                        'retention_expires_at' => $endsAt->copy()->addDays($retentionDays),
                        'retention_warned_at' => null,
                    ]);
                }
                $newStatus = MexcoCampusApplication::STATUS_RETENTION;
            }

            if ($newStatus !== $application->status) {
                $application->update(['status' => $newStatus]);
                $updated++;
            }
        }

        $standaloneLicenses = MexcoEventLicense::query()
            ->where('is_campus_sponsored', true)
            ->whereNotNull('event_id')
            ->with('event')
            ->get();

        foreach ($standaloneLicenses as $license) {
            $event = $license->event;
            if (! $event || $license->retention_expires_at) {
                continue;
            }

            if ($this->meetup->eventAvailability($event) === 'ended') {
                $endsAt = $event->ends_at ?? now();
                $license->update([
                    'retention_expires_at' => $endsAt->copy()->addDays($retentionDays),
                    'retention_warned_at' => null,
                ]);
                $updated++;
            }
        }

        return $updated;
    }

    public function processRetentionWarnings(): int
    {
        $warned = 0;
        $warningDays = config('mexco.campus.warning_days', [14, 7, 1]);

        $licenses = MexcoEventLicense::query()
            ->where('is_campus_sponsored', true)
            ->whereNotNull('retention_expires_at')
            ->where('retention_expires_at', '>', now())
            ->with('event')
            ->get();

        foreach ($licenses as $license) {
            $daysLeft = (int) now()->diffInDays($license->retention_expires_at, false);
            $flags = $license->retention_warned_at ?? [];

            foreach ($warningDays as $threshold) {
                if ($daysLeft <= $threshold && empty($flags[(string) $threshold])) {
                    $flags[(string) $threshold] = now()->toIso8601String();
                    $warned++;

                    Log::warning('Campus event retention warning', [
                        'event_id' => $license->event_id,
                        'event_name' => $license->event?->name,
                        'days_remaining' => max($daysLeft, 0),
                        'threshold' => $threshold,
                        'deletion_at' => $license->retention_expires_at?->toIso8601String(),
                    ]);

                    $application = MexcoCampusApplication::query()
                        ->where('event_license_id', $license->id)
                        ->orWhere('event_id', $license->event_id)
                        ->first();

                    $recipient = $application?->contact_email
                        ?? $license->event?->organiser_username;

                    if ($recipient) {
                        $this->notifications->send('campus_retention_warning', $recipient, [
                            'contact_name' => $application?->contact_person ?? 'Organiser',
                            'event_name' => $license->event?->name ?? $license->event_id,
                            'days_remaining' => (string) max($daysLeft, 0),
                            'deletion_date' => $license->retention_expires_at?->format('d M Y, H:i') ?? '',
                        ], MexcoEventLicense::class, $license->event_id, [
                            'threshold' => $threshold,
                        ]);
                    }
                }
            }

            if ($flags !== ($license->retention_warned_at ?? [])) {
                $license->update(['retention_warned_at' => $flags]);
            }
        }

        return $warned;
    }

    public function processRetentionDeletions(): int
    {
        $deleted = 0;

        $licenses = MexcoEventLicense::query()
            ->where('is_campus_sponsored', true)
            ->whereNotNull('retention_expires_at')
            ->where('retention_expires_at', '<=', now())
            ->get();

        foreach ($licenses as $license) {
            $eventId = $license->event_id;
            $application = MexcoCampusApplication::query()
                ->where('event_license_id', $license->id)
                ->orWhere('event_id', $eventId)
                ->first();

            if ($eventId && MeetupEvent::query()->where('id', $eventId)->exists()) {
                $this->meetup->deleteEvent($eventId);
            }

            if ($application) {
                $application->update([
                    'status' => MexcoCampusApplication::STATUS_DELETED,
                    'event_id' => null,
                    'event_license_id' => null,
                    'metadata' => array_merge($application->metadata ?? [], [
                        'deleted_at' => now()->toIso8601String(),
                        'former_event_id' => $eventId,
                    ]),
                ]);
            }

            $license->delete();
            $deleted++;

            Log::info('Campus event data deleted after retention', [
                'event_id' => $eventId,
                'application' => $application?->reference_no,
            ]);
        }

        return $deleted;
    }

    public function pendingApprovalCount(): int
    {
        return MexcoCampusApplication::query()
            ->whereIn('status', [
                MexcoCampusApplication::STATUS_SUBMITTED,
                MexcoCampusApplication::STATUS_UNDER_REVIEW,
            ])
            ->count();
    }

    public function dashboardStats(): array
    {
        $counts = MexcoCampusApplication::query()
            ->selectRaw('status, COUNT(*) as total')
            ->groupBy('status')
            ->pluck('total', 'status');

        $totalValue = MexcoCampusApplication::query()
            ->whereNotIn('status', [
                MexcoCampusApplication::STATUS_REJECTED,
                MexcoCampusApplication::STATUS_DELETED,
            ])
            ->sum('sponsorship_value_cents');

        $expiringSoon = MexcoEventLicense::query()
            ->where('is_campus_sponsored', true)
            ->whereNotNull('retention_expires_at')
            ->whereBetween('retention_expires_at', [now(), now()->addDays(14)])
            ->count();

        return [
            'counts' => $counts,
            'total_applications' => MexcoCampusApplication::query()->count(),
            'pending' => ($counts[MexcoCampusApplication::STATUS_SUBMITTED] ?? 0)
                + ($counts[MexcoCampusApplication::STATUS_UNDER_REVIEW] ?? 0),
            'approved' => ($counts[MexcoCampusApplication::STATUS_EVENT_CREATED] ?? 0)
                + ($counts[MexcoCampusApplication::STATUS_ACTIVE] ?? 0)
                + ($counts[MexcoCampusApplication::STATUS_COMPLETED] ?? 0)
                + ($counts[MexcoCampusApplication::STATUS_RETENTION] ?? 0),
            'rejected' => $counts[MexcoCampusApplication::STATUS_REJECTED] ?? 0,
            'retention' => $counts[MexcoCampusApplication::STATUS_RETENTION] ?? 0,
            'deleted' => $counts[MexcoCampusApplication::STATUS_DELETED] ?? 0,
            'total_sponsorship_value_cents' => (int) $totalValue,
            'expiring_soon' => $expiringSoon,
        ];
    }

    protected function assignCampusLicense(MeetupEvent $event, int $sponsorshipValueCents): MexcoEventLicense
    {
        $plan = MexcoPlan::query()->where('code', 'campus')->firstOrFail();

        $license = MexcoEventLicense::query()->updateOrCreate(
            ['event_id' => $event->id],
            [
                'plan_id' => $plan->id,
                'status' => 'active',
                'price_cents' => 0,
                'currency' => $plan->currency,
                'starts_at' => $event->starts_at ?? now(),
                'ends_at' => $event->ends_at,
                'activated_at' => now(),
                'is_campus_sponsored' => true,
                'sponsorship_value_cents' => $sponsorshipValueCents,
                'retention_expires_at' => null,
                'retention_warned_at' => null,
            ]
        );

        $this->entitlements->rebuildEntitlements($event->id);

        return $license->fresh(['plan']);
    }

    protected function resolveOrganisation(MexcoCampusApplication $application, array $options): MexcoOrganisation
    {
        if (! empty($options['organisation_id'])) {
            return MexcoOrganisation::query()->findOrFail($options['organisation_id']);
        }

        if ($application->organisation_id) {
            return MexcoOrganisation::query()->findOrFail($application->organisation_id);
        }

        $slug = Str::slug($application->institution) ?: 'campus-org';

        return MexcoOrganisation::query()->firstOrCreate(
            ['slug' => $slug],
            [
                'name' => $application->institution,
                'type' => 'university',
                'contact_person' => $application->contact_person,
                'email' => $application->contact_email,
                'status' => 'active',
            ]
        );
    }

    protected function eventWindow(MexcoCampusApplication $application, array $options): array
    {
        if (! empty($options['starts_at']) && ! empty($options['ends_at'])) {
            return [$options['starts_at'], $options['ends_at']];
        }

        if ($application->event_date) {
            $start = $application->event_date->copy()->startOfDay();
            $end = $application->event_date->copy()->endOfDay();

            return [$start, $end];
        }

        return [now(), now()->addDay()];
    }

    protected function generateReferenceNo(): string
    {
        do {
            $ref = 'CAMP-'.now()->format('Y').'-'.str_pad((string) random_int(1, 9999), 4, '0', STR_PAD_LEFT);
        } while (MexcoCampusApplication::query()->where('reference_no', $ref)->exists());

        return $ref;
    }

    protected function assertStatus(MexcoCampusApplication $application, array $allowed): void
    {
        if (! in_array($application->status, $allowed, true)) {
            throw ValidationException::withMessages([
                'status' => 'Application cannot be updated from status: '.$application->statusLabel(),
            ]);
        }
    }
}
