<?php

namespace App\Livewire;

use App\Services\FinanceRepository;
use App\Services\NotificationService;
use App\Services\PreferencesStore;
use App\Support\AppLocale;
use App\Support\CategoryCatalog;
use App\Support\DescriptionAutocomplete;
use App\Support\FinanceDataInsights;
use App\Support\SubscriptionInference;
use Carbon\Carbon;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Url;
use Livewire\Component;

#[Layout('layouts.app')]
class SubscriptionsPage extends Component
{
    /** @var list<array<string,mixed>>|null */
    private ?array $subscriptionHistoryCache = null;

    /** @var list<array<string,mixed>>|null */
    private ?array $transactionHistoryCache = null;

    /** @var list<string>|null */
    private ?array $subscriptionNameHistoryCache = null;

    #[Url]
    public ?string $create = null;

    #[Url(as: 'search')]
    public ?string $openSheet = null;

    public string $search = '';

    public bool $mobileSearchOpen = false;

    /** null | 'new' | id */
    public ?string $editing = null;

    public string $editName = '';

    public ?float $editCost = null;

    public string $editCategoryId = 'subscriptions';

    public string $editBillingCycle = 'monthly';

    public string $editStatus = 'active';

    public bool $editShared = false;

    public string $editSharedWith = '';

    public string $editNotes = '';

    public string $editNextRenewal = '';

    /** @var list<array{id: string, label: string, detail?: string, tone?: string}> */
    public array $smartSuggestions = [];

    /** @var list<array{value: string, hint?: string}> */
    public array $descriptionHints = [];

    public bool $manualCategory = false;

    public bool $manualBilling = false;

    public bool $manualStatus = false;

    public bool $manualShared = false;

    public bool $manualRenewal = false;

    /** True when the subscription row is owned by the current user (not only shared for viewing). */
    public bool $editIsOwner = true;

    public function mount(): void
    {
        if ($this->create === 'subscription') {
            $this->editing = 'new';
            $this->create = null;
            $this->bootstrapForm(null);
        }
        if ($this->openSheet === '1') {
            $this->mobileSearchOpen = true;
            $this->openSheet = null;
        }
    }

    public function openNew(): void
    {
        $this->editing = 'new';
        $this->bootstrapForm(null);
    }

    public function openEdit(string $id, FinanceRepository $finance): void
    {
        $sub = collect($finance->subscriptions())->firstWhere('id', $id);
        if (! $sub) {
            return;
        }
        $this->editing = $id;
        $this->bootstrapForm($sub);
    }

    public function closeModal(): void
    {
        $this->editing = null;
    }

    public function closeMobileSearch(): void
    {
        $this->mobileSearchOpen = false;
    }

    /**
     * @param  array<string, mixed>|null  $sub
     */
    protected function bootstrapForm(?array $sub): void
    {
        $this->manualCategory = false;
        $this->manualBilling = false;
        $this->manualStatus = false;
        $this->manualShared = false;
        $this->manualRenewal = false;

        if ($sub === null) {
            $this->editIsOwner = true;
            $this->editName = '';
            $this->editCost = null;
            $this->editCategoryId = 'subscriptions';
            $this->editBillingCycle = 'monthly';
            $this->editStatus = 'active';
            $this->editShared = false;
            $this->editSharedWith = '';
            $this->editNotes = '';
            $this->editNextRenewal = '';
        } else {
            $this->editIsOwner = (bool) ($sub['isOwner'] ?? true);
            $this->editName = (string) ($sub['name'] ?? '');
            $this->editCost = isset($sub['cost']) ? (float) $sub['cost'] : null;
            $this->editCategoryId = (string) ($sub['categoryId'] ?? 'subscriptions');
            $this->editBillingCycle = (string) ($sub['billingCycle'] ?? 'monthly');
            $this->editStatus = (string) ($sub['status'] ?? 'active');
            $this->editShared = (bool) ($sub['shared'] ?? false);
            $this->editSharedWith = isset($sub['sharedWith']) ? implode(', ', (array) $sub['sharedWith']) : '';
            $this->editNotes = (string) ($sub['notes'] ?? '');
            $this->editNextRenewal = (string) ($sub['nextRenewal'] ?? '');
        }
        $this->smartSuggestions = [];
        $this->descriptionHints = [];
    }

    protected function syncInference(): void
    {
        $inf = SubscriptionInference::infer($this->editName, $this->subscriptionHistory());
        $this->smartSuggestions = $inf['suggestions'] ?? [];
        if (! empty($inf['categoryId']) && ! $this->manualCategory) {
            $this->editCategoryId = $inf['categoryId'];
        }
        if (! empty($inf['billingCycle']) && ! $this->manualBilling) {
            $this->editBillingCycle = $inf['billingCycle'];
        }
        if (! empty($inf['status']) && ! $this->manualStatus) {
            $this->editStatus = $inf['status'];
        }
        if (isset($inf['shared']) && ! $this->manualShared) {
            $this->editShared = (bool) $inf['shared'];
        }
        if (! empty($inf['nextRenewal']) && ! $this->manualRenewal) {
            $this->editNextRenewal = $inf['nextRenewal'];
        }
        $this->descriptionHints = DescriptionAutocomplete::suggest(
            $this->editName,
            DescriptionAutocomplete::CONTEXT_SUBSCRIPTION,
            $this->subscriptionNameHistory(),
            12,
        );
    }

    /**
     * @return list<string>
     */
    protected function subscriptionNameHistory(): array
    {
        if ($this->subscriptionNameHistoryCache !== null) {
            return $this->subscriptionNameHistoryCache;
        }

        $this->subscriptionNameHistoryCache = collect($this->subscriptionHistory())
            ->pluck('name')
            ->merge(collect($this->transactionHistory())->pluck('name'))
            ->map(fn ($n) => trim((string) $n))
            ->filter()
            ->unique(fn (string $n) => mb_strtolower($n))
            ->values()
            ->all();

        return $this->subscriptionNameHistoryCache;
    }

    public function pickDescriptionHint(int $index): void
    {
        $row = $this->descriptionHints[$index] ?? null;
        if (! is_array($row) || ($row['value'] ?? '') === '') {
            return;
        }
        $this->editName = (string) $row['value'];
        $this->syncInference();
    }

    public function updatedEditName(): void
    {
        $this->syncInference();
    }

    public function setBillingCycle(string $c): void
    {
        $this->manualBilling = true;
        $this->editBillingCycle = $c;
    }

    public function setStatus(string $s): void
    {
        $this->manualStatus = true;
        $this->editStatus = $s;
    }

    public function setCategory(string $id): void
    {
        $this->manualCategory = true;
        $this->editCategoryId = $id;
    }

    public function toggleShared(): void
    {
        $this->manualShared = true;
        $this->editShared = ! $this->editShared;
    }

    public function updatedEditNextRenewal(): void
    {
        $this->manualRenewal = true;
    }

    public function saveSubscription(FinanceRepository $finance, NotificationService $notifications): void
    {
        if ($this->editing === null) {
            return;
        }
        $rows = collect($finance->subscriptions())
            ->filter(fn (array $sub): bool => (bool) ($sub['isOwner'] ?? false))
            ->keyBy('id');
        $id = $this->editing === 'new'
            ? 's'.(string) (int) round(microtime(true) * 1000)
            : $this->editing;
        $isNew = $this->editing === 'new';
        $existing = $rows->get($id);
        $prevRenewal = is_array($existing) ? (string) ($existing['nextRenewal'] ?? '') : '';
        $prevCost = is_array($existing) ? (float) ($existing['cost'] ?? 0) : 0.0;
        $prevStatus = is_array($existing) ? (string) ($existing['status'] ?? '') : '';
        $prevName = is_array($existing) ? (string) ($existing['name'] ?? '') : '';
        $prevCycle = is_array($existing) ? (string) ($existing['billingCycle'] ?? '') : '';

        $sharedWith = $this->editShared
            ? array_values(array_filter(array_map('trim', explode(',', $this->editSharedWith))))
            : [];

        $row = [
            'id' => $id,
            'name' => $this->editName,
            'categoryId' => $this->editCategoryId,
            'cost' => (float) ($this->editCost ?? 0),
            'billingCycle' => $this->editBillingCycle,
            'nextRenewal' => $this->editNextRenewal,
            'lastCharge' => is_array($existing) ? ($existing['lastCharge'] ?? '') : '',
            'status' => $this->editStatus,
            'confidence' => is_array($existing) ? ($existing['confidence'] ?? 100) : 100,
            'shared' => $this->editShared,
            'sharedWith' => $sharedWith,
            'usageScore' => is_array($existing) ? ($existing['usageScore'] ?? null) : null,
            'notes' => $this->editNotes,
        ];

        $rows->put($id, $row);
        $finance->setSubscriptions($rows->values()->all());
        $this->invalidateInferenceCaches();
        $user = auth()->user();
        if ($user !== null) {
            $currency = (string) ((PreferencesStore::all()['regional']['currency'] ?? 'EUR'));
            $cycleLabel = (string) (__('budgets.subscriptions_page.billing_cycle_labels.'.$this->editBillingCycle) ?? $this->editBillingCycle);

            if ($isNew) {
                $notifications->emit($user, 'subscription.created', [
                    'title' => __('notifications.types.subscription_created_title'),
                    'message' => __('notifications.types.subscription_created_message', ['name' => $this->editName]),
                    'entity_type' => 'subscription',
                    'entity_id' => $id,
                    'dedupe_key' => 'subscription:created:'.$id,
                ]);
            } else {
                $statusChanged = $prevStatus !== '' && $prevStatus !== $this->editStatus;
                $becameCanceled = $statusChanged
                    && in_array($prevStatus, ['active', 'trial'], true)
                    && $this->editStatus === 'inactive';

                if ($becameCanceled) {
                    $notifications->emit($user, 'subscription.canceled', [
                        'title' => __('notifications.types.subscription_canceled_title'),
                        'message' => __('notifications.types.subscription_canceled_message', ['name' => $this->editName]),
                        'entity_type' => 'subscription',
                        'entity_id' => $id,
                        'dedupe_key' => 'subscription:canceled:'.$id.':'.now()->format('Y-m-d-H'),
                    ]);
                } elseif ($prevRenewal !== '' && $this->editNextRenewal !== '' && $prevRenewal !== $this->editNextRenewal) {
                    $pretty = $this->editNextRenewal;
                    try {
                        $pretty = Carbon::parse($this->editNextRenewal)->toFormattedDateString();
                    } catch (\Throwable) {
                        // keep raw string
                    }
                    $notifications->emit($user, 'subscription.renewal_date_changed', [
                        'title' => __('notifications.types.subscription_renewal_date_changed_title'),
                        'message' => __('notifications.types.subscription_renewal_date_changed_message', [
                            'name' => $this->editName,
                            'date' => $pretty,
                        ]),
                        'entity_type' => 'subscription',
                        'entity_id' => $id,
                        'dedupe_key' => 'subscription:renewal:'.$id.':'.$this->editNextRenewal,
                    ]);
                } elseif (abs($prevCost - (float) ($this->editCost ?? 0)) > 0.009) {
                    $notifications->emit($user, 'subscription.cost_changed', [
                        'title' => __('notifications.types.subscription_cost_changed_title'),
                        'message' => __('notifications.types.subscription_cost_changed_message', [
                            'name' => $this->editName,
                            'amount' => number_format((float) ($this->editCost ?? 0), 2, '.', ','),
                            'currency' => $currency,
                            'cycle' => $cycleLabel,
                        ]),
                        'entity_type' => 'subscription',
                        'entity_id' => $id,
                        'dedupe_key' => 'subscription:cost:'.$id.':'.number_format((float) ($this->editCost ?? 0), 2, '.', ''),
                    ]);
                } elseif ($prevName !== '' && $prevName !== $this->editName) {
                    $notifications->emit($user, 'subscription.updated', [
                        'title' => __('notifications.types.subscription_updated_title'),
                        'message' => __('notifications.types.subscription_updated_message', ['name' => $this->editName]),
                        'entity_type' => 'subscription',
                        'entity_id' => $id,
                        'dedupe_key' => 'subscription:updated:'.$id.':'.now()->format('Y-m-d-H'),
                    ]);
                } elseif ($prevCycle !== '' && $prevCycle !== $this->editBillingCycle) {
                    $notifications->emit($user, 'subscription.updated', [
                        'title' => __('notifications.types.subscription_updated_title'),
                        'message' => __('notifications.types.subscription_updated_message', ['name' => $this->editName]),
                        'entity_type' => 'subscription',
                        'entity_id' => $id,
                        'dedupe_key' => 'subscription:updated:'.$id.':'.now()->format('Y-m-d-H'),
                    ]);
                }
            }
        }
        $this->closeModal();
    }

    public function deleteSubscription(NotificationService $notifications): void
    {
        if ($this->editing === null || $this->editing === 'new') {
            return;
        }
        $finance = app(FinanceRepository::class);
        $existing = collect($finance->subscriptions())->firstWhere('id', $this->editing);
        $name = is_array($existing) ? (string) ($existing['name'] ?? '') : '';
        $deleted = $finance->deleteSubscription($this->editing);
        if (! $deleted) {
            $msg = (string) __('budgets.subscriptions_page.toast_delete_failed');
            session()->flash('toast', $msg);
            $this->dispatch('app-toast', message: $msg);

            return;
        }
        $user = auth()->user();
        if ($user !== null && $name !== '') {
            $notifications->emit($user, 'subscription.deleted', [
                'title' => __('notifications.types.subscription_deleted_title'),
                'message' => __('notifications.types.subscription_deleted_message', ['name' => $name]),
                'entity_type' => 'subscription',
                'entity_id' => (string) $this->editing,
                'dedupe_key' => 'subscription:deleted:'.(string) $this->editing,
            ]);
        }
        $this->invalidateInferenceCaches();
        $this->closeModal();
        $msg = (string) __('budgets.subscriptions_page.toast_deleted');
        session()->flash('toast', $msg);
        $this->dispatch('app-toast', message: $msg);
    }

    public function render(FinanceRepository $finance)
    {
        $subs = $finance->subscriptions();
        $q = mb_strtolower(trim($this->search));
        $filtered = $q === ''
            ? $subs
            : array_values(array_filter($subs, fn ($s) => str_contains(mb_strtolower((string) ($s['name'] ?? '')), $q)));

        $active = array_values(array_filter($subs, fn ($s) => in_array($s['status'] ?? '', ['active', 'trial'], true)));
        $zombies = array_values(array_filter($subs, fn ($s) => ($s['status'] ?? '') === 'zombie'));

        $totalMonthly = 0.0;
        foreach ($active as $s) {
            $c = (float) ($s['cost'] ?? 0);
            $cycle = $s['billingCycle'] ?? 'monthly';
            $totalMonthly += match ($cycle) {
                'yearly' => $c / 12,
                'quarterly' => $c / 3,
                default => $c,
            };
        }

        $zombieYearly = collect($zombies)->sum(fn ($z) => (float) ($z['cost'] ?? 0)) * 12;

        $musicSubs = FinanceDataInsights::activeMusicSubscriptions($subs);

        $streamingSubs = array_values(array_filter($subs, function ($s) {
            if (($s['status'] ?? '') !== 'active') {
                return false;
            }
            $n = $s['name'] ?? '';

            return collect(['Netflix', 'Disney+', 'HBO', 'Hulu', 'Prime Video', 'Paramount', 'Apple TV'])->contains(fn ($m) => str_contains($n, $m));
        }));

        $sharedCount = count(array_filter($subs, fn ($s) => ! empty($s['shared'])));

        $adobeSubs = FinanceDataInsights::activeAdobeLikeSubscriptions($subs);
        $adobeYearlySavings = FinanceDataInsights::adobeAnnualSpend($subs);
        $musicYearlySavings = FinanceDataInsights::duplicateMusicYearlySavings($subs);
        $savingsTotal = $zombieYearly + $musicYearlySavings + $adobeYearlySavings;
        $savingsHasOpportunities = count($zombies) > 0 || $musicYearlySavings > 0.009 || $adobeYearlySavings > 0.009;
        $upcomingRenewals = (int) collect($active)->filter(function ($s) {
            try {
                $renewal = Carbon::parse((string) ($s['nextRenewal'] ?? ''));

                return $renewal->between(now()->startOfDay(), now()->copy()->addDays(7)->endOfDay());
            } catch (\Throwable) {
                return false;
            }
        })->count();
        $annualCommitment = $totalMonthly * 12;

        $locale = AppLocale::carbon();

        return view('livewire.subscriptions-page', [
            'filtered' => $filtered,
            'active' => $active,
            'zombies' => $zombies,
            'totalMonthly' => $totalMonthly,
            'zombieYearly' => $zombieYearly,
            'musicSubs' => $musicSubs,
            'musicYearlySavings' => $musicYearlySavings,
            'adobeSubs' => $adobeSubs,
            'adobeYearlySavings' => $adobeYearlySavings,
            'streamingSubs' => $streamingSubs,
            'sharedCount' => $sharedCount,
            'savingsTotal' => $savingsTotal,
            'savingsHasOpportunities' => $savingsHasOpportunities,
            'upcomingRenewals' => $upcomingRenewals,
            'annualCommitment' => $annualCommitment,
            'locale' => $locale,
            'categories' => CategoryCatalog::all(),
        ])->title((string) __('budgets.subscriptions_page.title'));
    }

    /** @return list<array<string,mixed>> */
    protected function subscriptionHistory(): array
    {
        if ($this->subscriptionHistoryCache !== null) {
            return $this->subscriptionHistoryCache;
        }

        $this->subscriptionHistoryCache = app(FinanceRepository::class)->subscriptions();

        return $this->subscriptionHistoryCache;
    }

    /** @return list<array<string,mixed>> */
    protected function transactionHistory(): array
    {
        if ($this->transactionHistoryCache !== null) {
            return $this->transactionHistoryCache;
        }

        $this->transactionHistoryCache = app(FinanceRepository::class)->transactions();

        return $this->transactionHistoryCache;
    }

    protected function invalidateInferenceCaches(): void
    {
        $this->subscriptionHistoryCache = null;
        $this->transactionHistoryCache = null;
        $this->subscriptionNameHistoryCache = null;
    }
}
