<?php

declare(strict_types=1);

namespace ImovelZapAi\Billing\Application\Actions;

use App\Models\User;
use ImovelZapAi\Billing\Application\Contracts\MercadoPagoClient;
use ImovelZapAi\Billing\Domain\Enums\BillingCycle;
use ImovelZapAi\Billing\Domain\Exceptions\BillingException;
use ImovelZapAi\Organizations\Domain\Enums\SubscriptionStatus;
use ImovelZapAi\Organizations\Infrastructure\Persistence\Eloquent\OrganizationSubscriptionModel;
use ImovelZapAi\Organizations\Infrastructure\Persistence\Eloquent\SubscriptionPlanModel;
use ImovelZapAi\Properties\Infrastructure\Persistence\Eloquent\PropertyModel;
use ImovelZapAi\Tenancy\Domain\Enums\OrganizationType;
use ImovelZapAi\Tenancy\Infrastructure\Persistence\Eloquent\TenantModel;

final class ChangePlanAction
{
    public function __construct(
        private readonly MercadoPagoClient $client,
        private readonly SyncMercadoPagoPlanCatalogAction $syncCatalog,
    ) {}

    public function execute(
        string $tenantId,
        string $planCode,
        ?BillingCycle $cycle = null,
    ): OrganizationSubscriptionModel {
        $this->syncCatalog->execute();

        $subscription = OrganizationSubscriptionModel::query()
            ->with('plan')
            ->where('tenant_id', $tenantId)
            ->first();

        if ($subscription === null) {
            throw BillingException::planNotFound($planCode);
        }

        $newPlan = SubscriptionPlanModel::query()
            ->where('code', $planCode)
            ->where('is_active', true)
            ->first();

        if ($newPlan === null) {
            throw BillingException::planNotFound($planCode);
        }

        $this->assertDowngradeAllowed($tenantId, $newPlan);

        $resolvedCycle = $cycle ?? BillingCycle::tryFrom((string) ($subscription->billing_cycle ?? 'monthly'))
            ?? BillingCycle::Monthly;

        $subscription->forceFill([
            'subscription_plan_id' => $newPlan->id,
            'billing_cycle' => $resolvedCycle->value,
            'status' => $subscription->status === SubscriptionStatus::Canceled
                ? SubscriptionStatus::Trialing
                : $subscription->status,
        ])->save();

        TenantModel::query()->whereKey($tenantId)->update([
            'type' => (int) $newPlan->max_users === 1
                ? OrganizationType::Solo
                : OrganizationType::Agency,
        ]);

        if (filled($subscription->mp_preapproval_id)) {
            $mpPlanId = $resolvedCycle === BillingCycle::Yearly
                ? $newPlan->mp_preapproval_plan_id_yearly
                : $newPlan->mp_preapproval_plan_id_monthly;

            if (filled($mpPlanId)) {
                $this->client->updatePreapproval((string) $subscription->mp_preapproval_id, [
                    'preapproval_plan_id' => $mpPlanId,
                ]);
            }
        }

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

    private function assertDowngradeAllowed(string $tenantId, SubscriptionPlanModel $plan): void
    {
        $users = User::query()->where('tenant_id', $tenantId)->count();
        $properties = PropertyModel::query()->withoutGlobalScopes()->where('tenant_id', $tenantId)->count();

        $maxUsers = (int) $plan->max_users;
        $maxProperties = (int) $plan->max_properties;

        if ($maxUsers > 0 && $users > $maxUsers) {
            throw BillingException::downgradeBlocked('usuário(s)', $users, $maxUsers);
        }

        if ($maxProperties > 0 && $properties > $maxProperties) {
            throw BillingException::downgradeBlocked('imóvel(is)', $properties, $maxProperties);
        }
    }
}
