<?php
namespace App\Services;

use App\Repositories\UserHierarchyRepository;

class UserHierarchyService
{
    private $repo;

    public function __construct(UserHierarchyRepository $repo)
    {
        $this->repo = $repo;
    }

    public function saveAssignment(array $payload, int $actorUserId = 0): int
    {
        $userId = (int)($payload['user_id'] ?? 0);
        $supervisorUserId = $this->nullablePositiveInt($payload['supervisor_user_id'] ?? null);
        $managerUserId = $this->nullablePositiveInt($payload['manager_user_id'] ?? null);
        $effectiveFrom = trim((string)($payload['effective_from'] ?? ''));
        $effectiveTo = trim((string)($payload['effective_to'] ?? ''));

        if ($userId <= 0 || !$this->repo->userExists($userId)) {
            throw new \InvalidArgumentException('کاربر معتبر نیست');
        }
        if ($supervisorUserId !== null && !$this->repo->userExists($supervisorUserId)) {
            throw new \InvalidArgumentException('سرپرست معتبر نیست');
        }
        if ($managerUserId !== null && !$this->repo->userExists($managerUserId)) {
            throw new \InvalidArgumentException('مدیر معتبر نیست');
        }
        if (!$this->isDate($effectiveFrom)) {
            throw new \InvalidArgumentException('تاریخ شروع معتبر نیست');
        }
        if ($effectiveTo !== '' && !$this->isDate($effectiveTo)) {
            throw new \InvalidArgumentException('تاریخ پایان معتبر نیست');
        }
        if ($effectiveTo !== '' && $effectiveTo < $effectiveFrom) {
            throw new \InvalidArgumentException('تاریخ پایان نباید قبل از تاریخ شروع باشد');
        }
        if ($supervisorUserId === $userId || $managerUserId === $userId) {
            throw new \InvalidArgumentException('کاربر نمی‌تواند سرپرست یا مدیر خودش باشد');
        }
        if ($supervisorUserId !== null && $managerUserId !== null && $supervisorUserId === $managerUserId) {
            throw new \InvalidArgumentException('سرپرست و مدیر نباید یکسان باشند');
        }
        if ($this->wouldCreateCycle($userId, $supervisorUserId, $managerUserId, $effectiveFrom)) {
            throw new \InvalidArgumentException('زنجیره مدیریتی نباید چرخه ایجاد کند');
        }

        return $this->repo->saveAssignment(
            $userId,
            $supervisorUserId,
            $managerUserId,
            $effectiveFrom,
            $effectiveTo !== '' ? $effectiveTo : null,
            $actorUserId > 0 ? $actorUserId : null
        );
    }

    public function listAssignments(int $userId = 0): array
    {
        return $this->repo->listAssignments($userId);
    }

    public function resolveSnapshot(int $userId, string $asOf): array
    {
        $asOf = $this->isDate($asOf) ? $asOf : date('Y-m-d');
        $row = $this->repo->findActiveForUser($userId, $asOf);
        return [
            'supervisor_user_id_snapshot' => $row ? ((int)($row['supervisor_user_id'] ?? 0) ?: null) : null,
            'manager_user_id_snapshot' => $row ? ((int)($row['manager_user_id'] ?? 0) ?: null) : null,
            'hierarchy_snapshot_at' => $asOf,
        ];
    }

    private function wouldCreateCycle(int $userId, ?int $supervisorUserId, ?int $managerUserId, string $asOf): bool
    {
        foreach ([$supervisorUserId, $managerUserId] as $parentId) {
            if ($parentId === null || $parentId <= 0) {
                continue;
            }
            $stack = [$parentId];
            $seen = [];
            while (!empty($stack)) {
                $currentId = (int)array_pop($stack);
                if ($currentId <= 0) {
                    continue;
                }
                if ($currentId === $userId) {
                    return true;
                }
                if (isset($seen[$currentId])) {
                    continue;
                }
                $seen[$currentId] = true;
                $row = $this->repo->findActiveForUser($currentId, $asOf);
                if (!$row) {
                    continue;
                }
                foreach (['supervisor_user_id', 'manager_user_id'] as $key) {
                    $next = (int)($row[$key] ?? 0);
                    if ($next > 0) {
                        $stack[] = $next;
                    }
                }
            }
        }
        return false;
    }

    private function nullablePositiveInt($value): ?int
    {
        $value = (int)$value;
        return $value > 0 ? $value : null;
    }

    private function isDate(string $value): bool
    {
        return preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) === 1;
    }
}
