<?php

namespace App\Services;

use App\Models\Allocation;
use App\Models\TeamMember;
use Carbon\Carbon;
use DateTimeInterface;
use Illuminate\Cache\Repository as CacheRepository;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Throwable;

class UtilizationService
{
    private ?bool $redisAvailable = null;

    public function __construct(
        private CapacityService $capacityService,
        private UtilizationFormatter $formatter
    ) {}

    /**
     * Calculate overall utilization for a team member in a specific month.
     * Overall utilization = (Allocated hours this month) / (Capacity this month) × 100%
     *
     * Results are cached for 1 hour.
     */
    public function calculateOverallUtilization(string $teamMemberId, string $month): array
    {
        $cacheKey = $this->buildCacheKey($teamMemberId, $month, 'overall');
        $tags = $this->getCacheTags($month, $teamMemberId);

        $resolver = function () use ($teamMemberId, $month): array {
            $capacityData = $this->capacityService->calculateIndividualCapacity($teamMemberId, $month);
            $capacity = $capacityData['hours'] ?? 0;

            $allocatedHours = Allocation::where('team_member_id', $teamMemberId)
                ->whereDate('month', $this->normalizeMonth($month))
                ->sum('allocated_hours');

            $utilization = $capacity > 0 ? ($allocatedHours / $capacity) * 100 : 0;

            return [
                'capacity' => round($capacity, 2),
                'allocated' => round($allocatedHours, 2),
                'utilization' => round($utilization, 1),
                'indicator' => $this->formatter->getIndicator($utilization),
            ];
        };

        return $this->rememberCache($cacheKey, now()->addHour(), $resolver, $tags);
    }

    /**
     * Calculate running utilization YTD for a team member.
     * Running utilization = (Allocated hours YTD) / (Capacity YTD) × 100%
     *
     * Results are cached for 1 hour.
     */
    public function calculateRunningUtilization(string $teamMemberId, string $month): array
    {
        $cacheKey = $this->buildCacheKey($teamMemberId, $month, 'running');
        $tags = $this->getCacheTags($month, $teamMemberId);

        $resolver = function () use ($teamMemberId, $month): array {
            $year = substr($month, 0, 4);
            $startMonth = "{$year}-01";
            $endMonth = $month;

            // Get all months from January to current month
            $months = $this->getMonthsInRange($startMonth, $endMonth);

            $totalCapacity = 0;
            $totalAllocated = 0;

            foreach ($months as $m) {
                $capacityData = $this->capacityService->calculateIndividualCapacity($teamMemberId, $m);
                $totalCapacity += $capacityData['hours'] ?? 0;

                $allocated = Allocation::where('team_member_id', $teamMemberId)
                    ->whereDate('month', $this->normalizeMonth($m))
                    ->sum('allocated_hours');
                $totalAllocated += $allocated;
            }

            $utilization = $totalCapacity > 0 ? ($totalAllocated / $totalCapacity) * 100 : 0;

            return [
                'capacity_ytd' => round($totalCapacity, 2),
                'allocated_ytd' => round($totalAllocated, 2),
                'utilization' => round($utilization, 1),
                'indicator' => $this->formatter->getIndicator($utilization),
                'months_included' => count($months),
            ];
        };

        return $this->rememberCache($cacheKey, now()->addHour(), $resolver, $tags);
    }

    /**
     * Get combined utilization data for a team member.
     */
    public function getUtilizationData(string $teamMemberId, string $month): array
    {
        return [
            'overall' => $this->calculateOverallUtilization($teamMemberId, $month),
            'running' => $this->calculateRunningUtilization($teamMemberId, $month),
        ];
    }

    /**
     * Calculate team-level utilization (average across active members).
     *
     * Results are cached for 1 hour.
     */
    public function calculateTeamUtilization(string $month): array
    {
        $cacheKey = "utilization:team:{$month}:overall";
        $tags = $this->getCacheTags($month);

        $resolver = function () use ($month): array {
            $activeMembers = TeamMember::where('active', true)->get();

            $utilizations = [];
            $totalUtilization = 0;
            $count = 0;

            foreach ($activeMembers as $member) {
                $data = $this->calculateOverallUtilization($member->id, $month);
                $utilizations[$member->id] = $data;
                $totalUtilization += $data['utilization'];
                $count++;
            }

            $averageUtilization = $count > 0 ? $totalUtilization / $count : 0;

            return [
                'average_utilization' => round($averageUtilization, 1),
                'average_indicator' => $this->formatter->getIndicator($averageUtilization),
                'member_count' => $count,
                'by_member' => $utilizations,
            ];
        };

        return $this->rememberCache($cacheKey, now()->addHour(), $resolver, $tags);
    }

    /**
     * Calculate team-level running utilization YTD.
     *
     * Results are cached for 1 hour.
     */
    public function calculateTeamRunningUtilization(string $month): array
    {
        $cacheKey = "utilization:team:{$month}:running";
        $tags = $this->getCacheTags($month);

        $resolver = function () use ($month): array {
            $activeMembers = TeamMember::where('active', true)->get();

            $utilizations = [];
            $totalUtilization = 0;
            $count = 0;

            foreach ($activeMembers as $member) {
                $data = $this->calculateRunningUtilization($member->id, $month);
                $utilizations[$member->id] = $data;
                $totalUtilization += $data['utilization'];
                $count++;
            }

            $averageUtilization = $count > 0 ? $totalUtilization / $count : 0;

            return [
                'average_utilization' => round($averageUtilization, 1),
                'average_indicator' => $this->formatter->getIndicator($averageUtilization),
                'member_count' => $count,
                'by_member' => $utilizations,
            ];
        };

        return $this->rememberCache($cacheKey, now()->addHour(), $resolver, $tags);
    }

    /**
     * Get utilization indicator color based on percentage.
     *
     * @deprecated Use UtilizationFormatter::getIndicator() instead
     */
    public function getUtilizationIndicator(float $utilization): string
    {
        return $this->formatter->getIndicator($utilization);
    }

    /**
     * Get utilization indicator color name for display.
     *
     * @deprecated Use UtilizationFormatter::getDisplayColor() instead
     */
    public function getUtilizationColor(float $utilization): string
    {
        return $this->formatter->getDisplayColor($utilization);
    }

    /**
     * Get utilization trend data for a team member over multiple months.
     */
    public function getUtilizationTrend(string $teamMemberId, string $startMonth, string $endMonth): array
    {
        $months = $this->getMonthsInRange($startMonth, $endMonth);
        $trend = [];

        foreach ($months as $month) {
            $overall = $this->calculateOverallUtilization($teamMemberId, $month);
            $trend[] = [
                'month' => $month,
                'utilization' => $overall['utilization'],
                'indicator' => $overall['indicator'],
                'capacity' => $overall['capacity'],
                'allocated' => $overall['allocated'],
            ];
        }

        return $trend;
    }

    /**
     * Clear cache for a specific team member and month(s).
     */
    public function forgetUtilizationCache(string $teamMemberId, array $months): void
    {
        foreach ($months as $month) {
            $tags = $this->getCacheTags($month, $teamMemberId);

            Cache::store('array')->forget($this->buildCacheKey($teamMemberId, $month, 'overall'));
            Cache::store('array')->forget($this->buildCacheKey($teamMemberId, $month, 'running'));

            if ($this->redisAvailable()) {
                $this->flushCacheTags($tags);
            }
        }
    }

    /**
     * Clear cache for an entire month (all team members).
     */
    public function forgetUtilizationCacheForMonth(string $month): void
    {
        Cache::store('array')->forget("utilization:team:{$month}:overall");
        Cache::store('array')->forget("utilization:team:{$month}:running");

        // Clear individual member caches
        foreach (TeamMember::pluck('id') as $teamMemberId) {
            Cache::store('array')->forget($this->buildCacheKey($teamMemberId, $month, 'overall'));
            Cache::store('array')->forget($this->buildCacheKey($teamMemberId, $month, 'running'));
        }

        if ($this->redisAvailable()) {
            $this->flushCacheTags($this->getCacheTags($month));
        }
    }

    /**
     * Build cache key for utilization data.
     */
    private function buildCacheKey(string $teamMemberId, string $month, string $type): string
    {
        return "utilization:{$type}:{$month}:{$teamMemberId}";
    }

    /**
     * Get cache tags for utilization data.
     */
    private function getCacheTags(string $month, ?string $teamMemberId = null): array
    {
        $tags = ['utilization', "utilization:{$month}"];

        if ($teamMemberId) {
            $tags[] = "utilization:member:{$teamMemberId}";
        }

        return $tags;
    }

    /**
     * Normalize month format to Y-m-01.
     */
    private function normalizeMonth(string $month): string
    {
        if (strlen($month) === 7) {
            return $month.'-01';
        }

        return $month;
    }

    /**
     * Get all months in a range (inclusive).
     *
     * @return array<string>
     */
    private function getMonthsInRange(string $startMonth, string $endMonth): array
    {
        $start = Carbon::createFromFormat('Y-m', $startMonth)->startOfMonth();
        $end = Carbon::createFromFormat('Y-m', $endMonth)->startOfMonth();

        $months = [];

        while ($start->lte($end)) {
            $months[] = $start->format('Y-m');
            $start->addMonth();
        }

        return $months;
    }

    /**
     * Remember value in cache with Redis/array fallback.
     */
    private function rememberCache(string $key, DateTimeInterface|int $ttl, callable $callback, array $tags = []): mixed
    {
        if (! $this->redisAvailable()) {
            return Cache::store('array')->remember($key, $ttl, $callback);
        }

        try {
            /** @var CacheRepository $store */
            $store = Cache::store('redis');

            if (! empty($tags)) {
                $store = $store->tags($tags);
            }

            return $store->remember($key, $ttl, $callback);
        } catch (Throwable) {
            return Cache::store('array')->remember($key, $ttl, $callback);
        }
    }

    /**
     * Flush cache tags (Redis only).
     */
    private function flushCacheTags(array $tags): void
    {
        if (! $this->redisAvailable()) {
            return;
        }

        try {
            /** @var CacheRepository $store */
            $store = Cache::store('redis');
            $store->tags($tags)->flush();
        } catch (Throwable) {
            // Ignore cache failures
        }
    }

    /**
     * Check if Redis is available for caching.
     */
    private function redisAvailable(): bool
    {
        if ($this->redisAvailable !== null) {
            return $this->redisAvailable;
        }

        if (! config('cache.stores.redis')) {
            return $this->redisAvailable = false;
        }

        $client = config('database.redis.client', 'phpredis');

        if ($client === 'predis') {
            return $this->redisAvailable = class_exists('\Predis\Client');
        }

        return $this->redisAvailable = extension_loaded('redis');
    }
}
