<?php

namespace App\Services\Billing;

use App\Models\AgencySubscriptionModel;
use App\Models\SubscriptionPlanModel;
use App\Models\SubscriptionUsageModel;
use App\Services\Support\ActivityLogger;
use App\Services\Support\AuditService;
use Config\Database;

/**
 * Owns subscription lifecycle and the limit-enforcement seam that Phase 3
 * left as TODOs (job posting limit, candidate view limit). See
 * 05-saas-architecture.md §2.
 */
class SubscriptionService
{
    private AgencySubscriptionModel $subscriptions;
    private SubscriptionPlanModel $plans;
    private SubscriptionUsageModel $usage;
    private ActivityLogger $activity;
    private AuditService $audit;

    public function __construct()
    {
        $this->subscriptions = new AgencySubscriptionModel();
        $this->plans         = new SubscriptionPlanModel();
        $this->usage         = new SubscriptionUsageModel();
        $this->activity      = new ActivityLogger();
        $this->audit         = new AuditService();
    }

    /**
     * Called once at agency registration — every agency starts on Free.
     */
    public function startFreeTrial(int $agencyId): array
    {
        $freePlan = $this->plans->findBySlug('free');
        if (! $freePlan) {
            throw new \RuntimeException('Free plan not seeded — run BillingSeeder.');
        }

        $subscriptionId = $this->subscriptions->insert([
            'agency_id'             => $agencyId,
            'plan_id'               => $freePlan['id'],
            'billing_cycle'         => 'monthly',
            'status'                => 'active',
            'current_period_start'  => date('Y-m-d'),
            'current_period_end'    => date('Y-m-d', strtotime('+30 days')),
            'auto_renew'            => 0,
            'created_at'            => date('Y-m-d H:i:s'),
        ], true);

        $this->createUsagePeriod($subscriptionId, date('Y-m-d'), date('Y-m-d', strtotime('+30 days')));

        return $this->subscriptions->find($subscriptionId);
    }

    public function changePlan(int $agencyId, string $newPlanSlug, string $billingCycle, int $changedByUserId): array
    {
        $newPlan = $this->plans->findBySlug($newPlanSlug);
        if (! $newPlan || ! $newPlan['is_active']) {
            throw new \InvalidArgumentException('Invalid or inactive plan selected.');
        }

        $current = $this->subscriptions->activeFor($agencyId);

        $db = Database::connect();
        $db->transStart();

        if ($current) {
            $this->subscriptions->update($current['id'], ['status' => 'cancelled', 'cancelled_at' => date('Y-m-d H:i:s')]);
        }

        $periodEnd = $billingCycle === 'yearly' ? date('Y-m-d', strtotime('+365 days')) : date('Y-m-d', strtotime('+30 days'));

        $newSubscriptionId = $this->subscriptions->insert([
            'agency_id'             => $agencyId,
            'plan_id'               => $newPlan['id'],
            'billing_cycle'         => $billingCycle,
            'status'                => 'active',
            'current_period_start'  => date('Y-m-d'),
            'current_period_end'    => $periodEnd,
            'auto_renew'            => 1,
            'created_at'            => date('Y-m-d H:i:s'),
        ], true);

        $this->createUsagePeriod($newSubscriptionId, date('Y-m-d'), $periodEnd);

        $db->transComplete();

        $this->audit->record($changedByUserId, 'subscription', 'plan_change', $agencyId, $current, ['plan_id' => $newPlan['id']]);
        $this->activity->log($changedByUserId, $agencyId, null, 'plan_change', ['new_plan' => $newPlanSlug]);

        return $this->subscriptions->find($newSubscriptionId);
    }

    /**
     * Super Admin override — resets the agency's current-period usage
     * counters (jobs posted, candidate views, contact reveals, resume
     * downloads) back to zero without changing their plan. Useful when an
     * agency legitimately needs more room this period but a full plan
     * change/upgrade isn't warranted.
     */
    public function resetUsage(int $agencyId, int $resetByUserId): void
    {
        $subscription = $this->subscriptions->activeFor($agencyId);
        if (! $subscription) {
            throw new \RuntimeException('Agency has no active subscription.');
        }
        $usage = $this->usage->currentFor($subscription['id']);
        if (! $usage) {
            throw new \RuntimeException('No usage period found for this subscription.');
        }

        $before = $usage;
        $this->usage->update($usage['id'], [
            'candidate_views_used'   => 0,
            'contact_reveals_used'   => 0,
            'resume_downloads_used'  => 0,
            'jobs_posted_used'       => 0,
        ]);

        $this->audit->record($resetByUserId, 'subscription_usage', 'reset', $agencyId, $before, ['reset' => true]);
        $this->activity->log($resetByUserId, $agencyId, null, 'usage_reset', []);
    }

    public function currentSubscriptionWithPlan(int $agencyId): ?array
    {
        $subscription = $this->subscriptions->activeFor($agencyId);
        if (! $subscription) {
            return null;
        }
        $plan = $this->plans->find($subscription['plan_id']);
        $usage = $this->usage->currentFor($subscription['id']);

        return ['subscription' => $subscription, 'plan' => $plan, 'usage' => $usage];
    }

    /**
     * Central limit gate. $counterColumn is one of the subscription_usage
     * columns; $limitColumn the matching subscription_plans column.
     * -1 on the plan means unlimited. Throws when the limit is exceeded —
     * callers (JobService, CandidateSearchController) catch and surface
     * as a 403 with an upgrade prompt.
     */
    public function assertWithinLimit(int $agencyId, string $counterColumn, string $limitColumn): array
    {
        $context = $this->currentSubscriptionWithPlan($agencyId);
        if (! $context) {
            throw new \RuntimeException('Agency has no active subscription.');
        }

        $limit = (int) $context['plan'][$limitColumn];
        if ($limit === -1) {
            return $context; // unlimited
        }

        $used = (int) ($context['usage'][$counterColumn] ?? 0);
        if ($used >= $limit) {
            throw new \App\Exceptions\PlanLimitExceededException(
                "Plan limit reached for {$limitColumn} ({$used}/{$limit}). Please upgrade your plan."
            );
        }

        return $context;
    }

    public function incrementUsage(int $agencyId, string $counterColumn): void
    {
        $context = $this->currentSubscriptionWithPlan($agencyId);
        if (! $context || ! $context['usage']) {
            return;
        }
        $this->usage->incrementCounter($context['usage']['id'], $counterColumn);
    }

    private function createUsagePeriod(int $subscriptionId, string $start, string $end): void
    {
        $this->usage->insert([
            'agency_subscription_id' => $subscriptionId,
            'period_start'           => $start,
            'period_end'             => $end,
        ]);
    }

    /**
     * Cron entry point (Phase 7 scheduler calls this daily). Expires
     * lapsed subscriptions and demotes the agency to Free-tier limits.
     */
    public function runExpiryCheck(): int
    {
        $expiring = $this->subscriptions->expiringBefore(date('Y-m-d'));
        $count = 0;

        foreach ($expiring as $sub) {
            $this->subscriptions->update($sub['id'], ['status' => 'expired']);
            $this->startFreeTrial((int) $sub['agency_id']); // demote to Free
            $count++;
        }

        return $count;
    }
}
