<?php

namespace App\Livewire\Reports;

use App\Enums\SubscriptionStatus;
use App\Enums\TenantStatus;
use App\Livewire\Concerns\WithDateRange;
use App\Models\SaasProduct;
use App\Models\Subscription;
use App\Models\Tenant;
use App\Support\Sql;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Livewire\Attributes\Title;
use Livewire\Attributes\Url;
use Livewire\Component;

/**
 * Are we growing, and are we keeping people?
 *
 * Trial conversion is the number that decides whether the free trial is doing
 * its job or simply giving the product away, so it is computed honestly:
 * only trials old enough to have ended are counted, otherwise last week's
 * signups drag the rate down and make a healthy funnel look broken.
 */
#[Title('Growth & churn')]
class Growth extends Component
{
    use WithDateRange;

    #[Url(except: '')]
    public string $productId = '';

    public function mount(): void
    {
        $this->mountWithDateRange('this_year');
    }

    protected function tenantQuery(): Builder
    {
        $query = Tenant::query();

        if ($this->productId !== '') {
            $query->where('saas_product_id', (int) $this->productId);
        }

        return $query;
    }

    /** Signups per month in the window. */
    public function signupsByMonth(): array
    {
        $query = (clone $this->tenantQuery());

        if ($from = $this->fromDate()) {
            $query->whereDate('created_at', '>=', $from);
        }

        if ($to = $this->toDate()) {
            $query->whereDate('created_at', '<=', $to);
        }

        return $query
            ->selectRaw(Sql::monthKey('created_at')." as ym, COUNT(*) as total")
            ->groupBy('ym')
            ->orderBy('ym')
            ->get()
            ->map(fn ($row) => [
                'label' => Carbon::createFromFormat('Y-m', $row->ym)->format('M y'),
                'value' => (int) $row->total,
            ])
            ->all();
    }

    /**
     * Trial → paid conversion.
     *
     * Only trials whose window has closed are in the denominator; a trial that
     * still has five days to run has not failed to convert yet.
     */
    public function conversion(): array
    {
        $settled = (clone $this->tenantQuery())
            ->whereNotNull('trial_ends_at')
            ->where('trial_ends_at', '<', now())
            ->count();

        $converted = (clone $this->tenantQuery())
            ->whereNotNull('trial_ends_at')
            ->where('trial_ends_at', '<', now())
            ->whereHas('subscriptions', fn (Builder $q) => $q
                ->where('is_trial', false)
                ->whereIn('status', [
                    SubscriptionStatus::Active->value,
                    SubscriptionStatus::Grace->value,
                    SubscriptionStatus::Expired->value,
                    SubscriptionStatus::Cancelled->value,
                ]))
            ->count();

        return [
            'settled' => $settled,
            'converted' => $converted,
            'rate' => $settled > 0 ? round($converted / $settled * 100, 1) : 0.0,
            'open' => (clone $this->tenantQuery())->where('status', TenantStatus::Trial)->count(),
        ];
    }

    /** Where accounts stand today. */
    public function statusMix(): array
    {
        return (clone $this->tenantQuery())
            ->selectRaw('status, COUNT(*) as total')
            ->groupBy('status')
            ->orderByDesc('total')
            ->get()
            ->map(fn ($row) => [
                'label' => TenantStatus::labelFor($row->status, 'Unknown'),
                'value' => (int) $row->total,
            ])
            ->all();
    }

    /**
     * Lapsed accounts: term ended, grace over, nothing bought since.
     *
     * This is the win-back list, which is a more useful artefact than a churn
     * percentage nobody can act on.
     */
    public function lapsed()
    {
        return Subscription::query()
            ->with(['tenant', 'plan'])
            ->when($this->productId !== '', fn ($q) => $q->where('saas_product_id', (int) $this->productId))
            ->where('is_trial', false)
            ->whereNotNull('ends_at')
            ->where('ends_at', '<', now())
            ->whereNotIn('status', [SubscriptionStatus::Cancelled->value])
            ->whereDoesntHave('tenant.subscriptions', fn (Builder $q) => $q
                ->where('ends_at', '>', now()))
            ->orderByDesc('ends_at')
            ->limit(20)
            ->get();
    }

    public function render()
    {
        $signups = $this->signupsByMonth();

        return view('livewire.reports.growth', [
            'signups' => $signups,
            'conversion' => $this->conversion(),
            'statusMix' => $this->statusMix(),
            'lapsed' => $this->lapsed(),
            'products' => SaasProduct::orderBy('name')->pluck('name', 'id'),
            'totalSignups' => array_sum(array_column($signups, 'value')),
        ]);
    }
}
