<?php

namespace Core\Mod\Tenant\Services;

use Core\Mod\Tenant\Models\Boost;
use Core\Mod\Tenant\Models\EntitlementLog;
use Core\Mod\Tenant\Models\Feature;
use Core\Mod\Tenant\Models\Package;
use Core\Mod\Tenant\Models\UsageRecord;
use Core\Mod\Tenant\Models\Workspace;
use Core\Mod\Tenant\Models\WorkspacePackage;
use Core\Mod\Tenant\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;

class EntitlementService
{
    /**
     * Cache TTL in seconds.
     */
    protected const CACHE_TTL = 300; // 5 minutes

    /**
     * Check if a workspace can use a feature.
     */
    public function can(Workspace $workspace, string $featureCode, int $quantity = 1): EntitlementResult
    {
        $feature = $this->getFeature($featureCode);

        if (! $feature) {
            return EntitlementResult::denied(
                reason: "Feature '{$featureCode}' does not exist.",
                featureCode: $featureCode
            );
        }

        // Get the pool feature code (parent if hierarchical)
        $poolFeatureCode = $feature->getPoolFeatureCode();

        // Get total limit from all active packages + boosts
        $totalLimit = $this->getTotalLimit($workspace, $poolFeatureCode);

        if ($totalLimit === null) {
            // Feature not included in any package
            return EntitlementResult::denied(
                reason: "Your plan does not include {$feature->name}.",
                featureCode: $featureCode
            );
        }

        // Check for unlimited
        if ($totalLimit === -1) {
            return EntitlementResult::unlimited($featureCode);
        }

        // For boolean features, just check if enabled
        if ($feature->isBoolean()) {
            return EntitlementResult::allowed(featureCode: $featureCode);
        }

        // Get current usage
        $currentUsage = $this->getCurrentUsage($workspace, $poolFeatureCode, $feature);

        // Check if quantity would exceed limit
        if ($currentUsage + $quantity > $totalLimit) {
            return EntitlementResult::denied(
                reason: "You've reached your {$feature->name} limit ({$totalLimit}).",
                limit: $totalLimit,
                used: $currentUsage,
                featureCode: $featureCode
            );
        }

        return EntitlementResult::allowed(
            limit: $totalLimit,
            used: $currentUsage,
            featureCode: $featureCode
        );
    }

    /**
     * Record usage of a feature.
     */
    public function recordUsage(
        Workspace $workspace,
        string $featureCode,
        int $quantity = 1,
        ?User $user = null,
        ?array $metadata = null
    ): UsageRecord {
        $feature = $this->getFeature($featureCode);
        $poolFeatureCode = $feature?->getPoolFeatureCode() ?? $featureCode;

        $record = UsageRecord::create([
            'workspace_id' => $workspace->id,
            'feature_code' => $poolFeatureCode,
            'quantity' => $quantity,
            'user_id' => $user?->id,
            'metadata' => $metadata,
            'recorded_at' => now(),
        ]);

        // Invalidate cache
        $this->invalidateCache($workspace);

        return $record;
    }

    /**
     * Provision a package for a workspace.
     */
    public function provisionPackage(
        Workspace $workspace,
        string $packageCode,
        array $options = []
    ): WorkspacePackage {
        $package = Package::where('code', $packageCode)->firstOrFail();

        // Check if this is a base package and workspace already has one
        if ($package->is_base_package) {
            $existingBase = $workspace->workspacePackages()
                ->whereHas('package', fn ($q) => $q->where('is_base_package', true))
                ->active()
                ->first();

            if ($existingBase) {
                // Cancel existing base package
                $existingBase->cancel(now());

                EntitlementLog::logPackageAction(
                    $workspace,
                    EntitlementLog::ACTION_PACKAGE_CANCELLED,
                    $existingBase,
                    source: $options['source'] ?? EntitlementLog::SOURCE_SYSTEM,
                    metadata: ['reason' => 'Replaced by new base package']
                );
            }
        }

        $workspacePackage = WorkspacePackage::create([
            'workspace_id' => $workspace->id,
            'package_id' => $package->id,
            'status' => WorkspacePackage::STATUS_ACTIVE,
            'starts_at' => $options['starts_at'] ?? now(),
            'expires_at' => $options['expires_at'] ?? null,
            'billing_cycle_anchor' => $options['billing_cycle_anchor'] ?? now(),
            'blesta_service_id' => $options['blesta_service_id'] ?? null,
            'metadata' => $options['metadata'] ?? null,
        ]);

        EntitlementLog::logPackageAction(
            $workspace,
            EntitlementLog::ACTION_PACKAGE_PROVISIONED,
            $workspacePackage,
            source: $options['source'] ?? EntitlementLog::SOURCE_SYSTEM,
            newValues: $workspacePackage->toArray()
        );

        $this->invalidateCache($workspace);

        return $workspacePackage;
    }

    /**
     * Provision a boost for a workspace.
     */
    public function provisionBoost(
        Workspace $workspace,
        string $featureCode,
        array $options = []
    ): Boost {
        $boost = Boost::create([
            'workspace_id' => $workspace->id,
            'feature_code' => $featureCode,
            'boost_type' => $options['boost_type'] ?? Boost::BOOST_TYPE_ADD_LIMIT,
            'duration_type' => $options['duration_type'] ?? Boost::DURATION_CYCLE_BOUND,
            'limit_value' => $options['limit_value'] ?? null,
            'consumed_quantity' => 0,
            'status' => Boost::STATUS_ACTIVE,
            'starts_at' => $options['starts_at'] ?? now(),
            'expires_at' => $options['expires_at'] ?? null,
            'blesta_addon_id' => $options['blesta_addon_id'] ?? null,
            'metadata' => $options['metadata'] ?? null,
        ]);

        EntitlementLog::logBoostAction(
            $workspace,
            EntitlementLog::ACTION_BOOST_PROVISIONED,
            $boost,
            source: $options['source'] ?? EntitlementLog::SOURCE_SYSTEM,
            newValues: $boost->toArray()
        );

        $this->invalidateCache($workspace);

        return $boost;
    }

    /**
     * Get usage summary for a workspace.
     */
    public function getUsageSummary(Workspace $workspace): Collection
    {
        $features = Feature::active()->orderBy('category')->orderBy('sort_order')->get();
        $summary = collect();

        foreach ($features as $feature) {
            $result = $this->can($workspace, $feature->code);

            $summary->push([
                'feature' => $feature,
                'code' => $feature->code,
                'name' => $feature->name,
                'category' => $feature->category,
                'type' => $feature->type,
                'allowed' => $result->isAllowed(),
                'limit' => $result->limit,
                'used' => $result->used,
                'remaining' => $result->remaining,
                'unlimited' => $result->isUnlimited(),
                'percentage' => $result->getUsagePercentage(),
                'near_limit' => $result->isNearLimit(),
            ]);
        }

        return $summary->groupBy('category');
    }

    /**
     * Get all active packages for a workspace.
     */
    public function getActivePackages(Workspace $workspace): Collection
    {
        return $workspace->workspacePackages()
            ->with('package.features')
            ->active()
            ->notExpired()
            ->get();
    }

    /**
     * Get all active boosts for a workspace.
     */
    public function getActiveBoosts(Workspace $workspace): Collection
    {
        return $workspace->boosts()
            ->usable()
            ->orderBy('expires_at')
            ->get();
    }

    /**
     * Suspend a workspace's packages (e.g. for non-payment).
     */
    public function suspendWorkspace(Workspace $workspace, ?string $source = null): void
    {
        $packages = $workspace->workspacePackages()->active()->get();

        foreach ($packages as $workspacePackage) {
            $workspacePackage->suspend();

            EntitlementLog::logPackageAction(
                $workspace,
                EntitlementLog::ACTION_PACKAGE_SUSPENDED,
                $workspacePackage,
                source: $source ?? EntitlementLog::SOURCE_SYSTEM
            );
        }

        $this->invalidateCache($workspace);
    }

    /**
     * Reactivate a workspace's packages.
     */
    public function reactivateWorkspace(Workspace $workspace, ?string $source = null): void
    {
        $packages = $workspace->workspacePackages()
            ->where('status', WorkspacePackage::STATUS_SUSPENDED)
            ->get();

        foreach ($packages as $workspacePackage) {
            $workspacePackage->reactivate();

            EntitlementLog::logPackageAction(
                $workspace,
                EntitlementLog::ACTION_PACKAGE_REACTIVATED,
                $workspacePackage,
                source: $source ?? EntitlementLog::SOURCE_SYSTEM
            );
        }

        $this->invalidateCache($workspace);
    }

    /**
     * Revoke a package from a workspace (e.g. subscription cancelled).
     */
    public function revokePackage(Workspace $workspace, string $packageCode, ?string $source = null): void
    {
        $workspacePackage = $workspace->workspacePackages()
            ->whereHas('package', fn ($q) => $q->where('code', $packageCode))
            ->active()
            ->first();

        if (! $workspacePackage) {
            return;
        }

        $workspacePackage->update([
            'status' => WorkspacePackage::STATUS_CANCELLED,
            'expires_at' => now(),
        ]);

        EntitlementLog::logPackageAction(
            $workspace,
            EntitlementLog::ACTION_PACKAGE_CANCELLED,
            $workspacePackage,
            source: $source ?? EntitlementLog::SOURCE_SYSTEM,
            metadata: ['reason' => 'Package revoked']
        );

        $this->invalidateCache($workspace);
    }

    /**
     * Get the total limit for a feature across all packages + boosts.
     *
     * Returns null if feature not included, -1 if unlimited.
     */
    protected function getTotalLimit(Workspace $workspace, string $featureCode): ?int
    {
        $cacheKey = "entitlement:{$workspace->id}:limit:{$featureCode}";

        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($workspace, $featureCode) {
            $feature = $this->getFeature($featureCode);

            if (! $feature) {
                return null;
            }

            $totalLimit = 0;
            $hasFeature = false;

            // Sum limits from active packages
            $packages = $this->getActivePackages($workspace);

            foreach ($packages as $workspacePackage) {
                $packageFeature = $workspacePackage->package->features
                    ->where('code', $featureCode)
                    ->first();

                if ($packageFeature) {
                    $hasFeature = true;

                    // Check if unlimited in this package
                    if ($packageFeature->type === Feature::TYPE_UNLIMITED) {
                        return -1;
                    }

                    // Add limit value (null = boolean, no limit to add)
                    $limitValue = $packageFeature->pivot->limit_value;
                    if ($limitValue !== null) {
                        $totalLimit += $limitValue;
                    }
                }
            }

            // Add limits from active boosts
            $boosts = $workspace->boosts()
                ->forFeature($featureCode)
                ->usable()
                ->get();

            foreach ($boosts as $boost) {
                $hasFeature = true;

                if ($boost->boost_type === Boost::BOOST_TYPE_UNLIMITED) {
                    return -1;
                }

                if ($boost->boost_type === Boost::BOOST_TYPE_ADD_LIMIT) {
                    $remaining = $boost->getRemainingLimit();
                    if ($remaining !== null) {
                        $totalLimit += $remaining;
                    }
                }
            }

            return $hasFeature ? $totalLimit : null;
        });
    }

    /**
     * Get current usage for a feature.
     */
    protected function getCurrentUsage(Workspace $workspace, string $featureCode, Feature $feature): int
    {
        $cacheKey = "entitlement:{$workspace->id}:usage:{$featureCode}";

        return Cache::remember($cacheKey, 60, function () use ($workspace, $featureCode, $feature) {
            // Determine the time window for usage calculation
            if ($feature->resetsMonthly()) {
                // Get billing cycle anchor from the primary package
                $primaryPackage = $workspace->workspacePackages()
                    ->whereHas('package', fn ($q) => $q->where('is_base_package', true))
                    ->active()
                    ->first();

                $cycleStart = $primaryPackage
                    ? $primaryPackage->getCurrentCycleStart()
                    : now()->startOfMonth();

                return UsageRecord::getTotalUsage($workspace->id, $featureCode, $cycleStart);
            }

            if ($feature->resetsRolling()) {
                $days = $feature->rolling_window_days ?? 30;

                return UsageRecord::getRollingUsage($workspace->id, $featureCode, $days);
            }

            // No reset - all time usage
            return UsageRecord::getTotalUsage($workspace->id, $featureCode);
        });
    }

    /**
     * Get a feature by code.
     */
    protected function getFeature(string $code): ?Feature
    {
        return Cache::remember("feature:{$code}", self::CACHE_TTL, function () use ($code) {
            return Feature::where('code', $code)->first();
        });
    }

    /**
     * Invalidate all entitlement caches for a workspace.
     */
    public function invalidateCache(Workspace $workspace): void
    {
        // We can't easily clear pattern-based cache keys with all drivers,
        // so we use a version tag approach
        Cache::forget("entitlement:{$workspace->id}:version");
        Cache::increment("entitlement:{$workspace->id}:version");

        // For now, just clear specific known keys
        $features = Feature::pluck('code');
        foreach ($features as $code) {
            Cache::forget("entitlement:{$workspace->id}:limit:{$code}");
            Cache::forget("entitlement:{$workspace->id}:usage:{$code}");
        }
    }

    /**
     * Expire cycle-bound boosts at billing cycle end.
     */
    public function expireCycleBoundBoosts(Workspace $workspace): void
    {
        $boosts = $workspace->boosts()
            ->where('duration_type', Boost::DURATION_CYCLE_BOUND)
            ->where('status', Boost::STATUS_ACTIVE)
            ->get();

        foreach ($boosts as $boost) {
            $boost->expire();

            EntitlementLog::logBoostAction(
                $workspace,
                EntitlementLog::ACTION_BOOST_EXPIRED,
                $boost,
                source: EntitlementLog::SOURCE_SYSTEM,
                metadata: ['reason' => 'Billing cycle ended']
            );
        }

        $this->invalidateCache($workspace);
    }
}
