<?php

namespace App\Services;

use App\Models\TeamMember;
use Closure;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Throwable;

/**
 * Team Member Service
 *
 * Handles business logic for team member operations.
 */
class TeamMemberService
{
    private ?bool $redisAvailable = null;

    /**
     * Get all team members with optional filtering.
     *
     * @param  bool|null  $active  Filter by active status
     * @return Collection<TeamMember>
     */
    public function getAll(?bool $active = null): Collection
    {
        /** @var Collection<TeamMember> $teamMembers */
        $teamMembers = $this->rememberTeamMembers(
            $this->buildTeamMembersCacheKey($active),
            now()->addHour(),
            function () use ($active): Collection {
                $query = TeamMember::with('role');

                if ($active !== null) {
                    $query->where('active', $active);
                }

                return $query->get();
            }
        );

        return $teamMembers;
    }

    /**
     * Find a team member by ID.
     */
    public function findById(string $id): ?TeamMember
    {
        return TeamMember::with('role')->find($id);
    }

    /**
     * Create a new team member.
     *
     * @throws ValidationException
     */
    public function create(array $data): TeamMember
    {
        $validator = Validator::make($data, [
            'name' => 'required|string|max:255',
            'role_id' => 'required|integer|exists:roles,id',
            'hourly_rate' => 'required|numeric|gt:0',
            'active' => 'boolean',
        ], [
            'hourly_rate.gt' => 'Hourly rate must be greater than 0',
        ]);

        if ($validator->fails()) {
            throw new ValidationException($validator);
        }

        $teamMember = TeamMember::create([
            'name' => $data['name'],
            'role_id' => $data['role_id'],
            'hourly_rate' => $data['hourly_rate'],
            'active' => $data['active'] ?? true,
        ]);

        $teamMember->load('role');
        $this->forgetTeamMembersCache();

        return $teamMember;
    }

    /**
     * Update an existing team member.
     *
     * @throws ValidationException
     */
    public function update(TeamMember $teamMember, array $data): TeamMember
    {
        $validator = Validator::make($data, [
            'name' => 'sometimes|string|max:255',
            'role_id' => 'sometimes|integer|exists:roles,id',
            'hourly_rate' => 'sometimes|numeric|gt:0',
            'active' => 'sometimes|boolean',
        ], [
            'hourly_rate.gt' => 'Hourly rate must be greater than 0',
        ]);

        if ($validator->fails()) {
            throw new ValidationException($validator);
        }

        $teamMember->update($data);
        $teamMember->load('role');
        $this->forgetTeamMembersCache();

        return $teamMember;
    }

    /**
     * Delete a team member.
     *
     * @throws \RuntimeException
     */
    public function delete(TeamMember $teamMember): void
    {
        // Check if team member has allocations
        if ($teamMember->allocations()->exists()) {
            throw new \RuntimeException(
                'Cannot delete team member with active allocations',
                422
            );
        }

        // Check if team member has actuals
        if ($teamMember->actuals()->exists()) {
            throw new \RuntimeException(
                'Cannot delete team member with historical data',
                422
            );
        }

        $teamMember->delete();
        $this->forgetTeamMembersCache();
    }

    /**
     * Check if a team member can be deleted.
     *
     * @return array{canDelete: bool, reason?: string}
     */
    public function canDelete(TeamMember $teamMember): array
    {
        if ($teamMember->allocations()->exists()) {
            return [
                'canDelete' => false,
                'reason' => 'Team member has active allocations',
            ];
        }

        if ($teamMember->actuals()->exists()) {
            return [
                'canDelete' => false,
                'reason' => 'Team member has historical data',
            ];
        }

        return ['canDelete' => true];
    }

    private function buildTeamMembersCacheKey(?bool $active): string
    {
        if ($active === null) {
            return 'team-members:all';
        }

        return $active ? 'team-members:active' : 'team-members:inactive';
    }

    /**
     * @param  Closure(): Collection<TeamMember>  $callback
     * @return Collection<TeamMember>
     */
    private function rememberTeamMembers(string $key, DateTimeInterface|int $ttl, Closure $callback): Collection
    {
        if (! $this->redisAvailable()) {
            /** @var Collection<TeamMember> $payload */
            $payload = Cache::store('array')->remember($key, $ttl, $callback);

            return $payload;
        }

        try {
            /** @var Collection<TeamMember> $payload */
            $payload = Cache::store('redis')->remember($key, $ttl, $callback);

            return $payload;
        } catch (Throwable) {
            /** @var Collection<TeamMember> $payload */
            $payload = Cache::store('array')->remember($key, $ttl, $callback);

            return $payload;
        }
    }

    private function forgetTeamMembersCache(): void
    {
        Cache::store('array')->forget($this->buildTeamMembersCacheKey(null));
        Cache::store('array')->forget($this->buildTeamMembersCacheKey(true));
        Cache::store('array')->forget($this->buildTeamMembersCacheKey(false));

        if (! $this->redisAvailable()) {
            return;
        }

        try {
            Cache::store('redis')->forget($this->buildTeamMembersCacheKey(null));
            Cache::store('redis')->forget($this->buildTeamMembersCacheKey(true));
            Cache::store('redis')->forget($this->buildTeamMembersCacheKey(false));
        } catch (Throwable) {
            // Ignore cache failures when Redis is unavailable.
        }
    }

    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');
    }
}
