<?php

namespace App\Actions;

use App\Enums\SubscriptionStatus;
use App\Enums\TenantStatus;
use App\Models\Tenant;
use App\Services\ActivityLogger;
use Illuminate\Support\Facades\DB;
use RuntimeException;

/**
 * Approving, suspending and reinstating a shop account.
 *
 * Suspension is the panel's blunt instrument — it overrides the subscription
 * entirely, because "this shop is abusing the service" and "this shop has not
 * paid" are different problems with different remedies.
 */
class ManageTenant
{
    public function __construct(protected ActivityLogger $activity) {}

    /**
     * Let a pending signup in.
     *
     * Only relevant when manual approval is switched on; the default flow puts
     * new shops straight onto a trial.
     */
    public function approve(Tenant $tenant, ?string $note = null): Tenant
    {
        if ($tenant->status === TenantStatus::Suspended) {
            throw new RuntimeException('This account is suspended. Reinstate it instead of approving it.');
        }

        return DB::transaction(function () use ($tenant, $note) {
            $subscription = $tenant->currentSubscription();

            $tenant->forceFill([
                'status' => $subscription?->is_trial ? TenantStatus::Trial : TenantStatus::Active,
                'approved_at' => $tenant->approved_at ?? now(),
                'admin_notes' => $this->appendNote($tenant->admin_notes, $note),
            ])->save();

            $this->activity->log(
                'tenant.approved',
                "Approved {$tenant->business_name}",
                $tenant,
                [],
                (int) $tenant->id,
            );

            return $tenant->refresh();
        });
    }

    /**
     * Refuse a signup outright.
     *
     * Kept separate from suspend so the reporting can tell "never became a
     * customer" apart from "was a customer and had to be cut off".
     */
    public function reject(Tenant $tenant, string $reason): Tenant
    {
        $this->requireReason($reason);

        return DB::transaction(function () use ($tenant, $reason) {
            $tenant->forceFill([
                'status' => TenantStatus::Rejected,
                'suspension_reason' => $reason,
            ])->save();

            $tenant->subscriptions()
                ->whereNotIn('status', [SubscriptionStatus::Cancelled->value])
                ->update([
                    'status' => SubscriptionStatus::Rejected->value,
                    'cancelled_at' => now(),
                    'cancellation_reason' => $reason,
                ]);

            $this->activity->log(
                'tenant.rejected',
                "Rejected {$tenant->business_name} — {$reason}",
                $tenant,
                ['reason' => $reason],
                (int) $tenant->id,
            );

            return $tenant->refresh();
        });
    }

    /**
     * Cut a shop off immediately.
     *
     * The subscription rows are left untouched: if the suspension is lifted
     * tomorrow, the tenant gets back exactly the term they had paid for rather
     * than an argument about missing days.
     */
    public function suspend(Tenant $tenant, string $reason): Tenant
    {
        $this->requireReason($reason);

        if ($tenant->status === TenantStatus::Suspended) {
            throw new RuntimeException('This account is already suspended.');
        }

        return DB::transaction(function () use ($tenant, $reason) {
            $tenant->forceFill([
                'status' => TenantStatus::Suspended,
                'suspended_at' => now(),
                'suspension_reason' => $reason,
            ])->save();

            $this->activity->log(
                'tenant.suspended',
                "Suspended {$tenant->business_name} — {$reason}",
                $tenant,
                ['reason' => $reason],
                (int) $tenant->id,
            );

            return $tenant->refresh();
        });
    }

    /** Undo a suspension and restore whatever standing the dates imply. */
    public function reinstate(Tenant $tenant, ?string $note = null): Tenant
    {
        return DB::transaction(function () use ($tenant, $note) {
            $subscription = $tenant->currentSubscription();

            $status = match (true) {
                $subscription === null => TenantStatus::Pending,
                $subscription->onTrial() => TenantStatus::Trial,
                $subscription->effectiveStatus() === SubscriptionStatus::Expired => TenantStatus::Expired,
                default => TenantStatus::Active,
            };

            $tenant->forceFill([
                'status' => $status,
                'suspended_at' => null,
                'suspension_reason' => null,
                'admin_notes' => $this->appendNote($tenant->admin_notes, $note),
            ])->save();

            $this->activity->log(
                'tenant.reinstated',
                "Reinstated {$tenant->business_name}",
                $tenant,
                [],
                (int) $tenant->id,
            );

            return $tenant->refresh();
        });
    }

    /** Free-form note the team leaves on an account. */
    public function note(Tenant $tenant, string $note): Tenant
    {
        $this->requireReason($note);

        $tenant->forceFill([
            'admin_notes' => $this->appendNote($tenant->admin_notes, $note),
        ])->save();

        $this->activity->log(
            'tenant.noted',
            "Note on {$tenant->business_name}: {$note}",
            $tenant,
            [],
            (int) $tenant->id,
        );

        return $tenant->refresh();
    }

    /*
    |--------------------------------------------------------------------------
    | Internals
    |--------------------------------------------------------------------------
    */

    protected function requireReason(string $reason): void
    {
        if (trim($reason) === '') {
            throw new RuntimeException('Please write a reason — it becomes part of the account history.');
        }
    }

    /** Notes are appended, never overwritten: account history is evidence. */
    protected function appendNote(?string $existing, ?string $note): ?string
    {
        if ($note === null || trim($note) === '') {
            return $existing;
        }

        $stamp = now()->format('d-m-Y H:i').' · '.(auth()->user()?->name ?? 'system');

        return trim(($existing ? $existing."\n\n" : '')."[{$stamp}] ".trim($note));
    }
}
