<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Auth;
use App\Core\Database;
use App\Repositories\UserRepository;
use InvalidArgumentException;
use RuntimeException;

final class UserService
{
    public function __construct(private ?UserRepository $users = null)
    {
        $this->users ??= new UserRepository();
    }

    public function list(array $filters): array
    {
        $visibilityRole = Auth::hasRole('SUPER_ADMIN') ? null : (Auth::hasRole('ADMIN') ? 'THERAPIST' : '__NONE__');
        $result = $this->users->paginate(
            trim((string) ($filters['search'] ?? '')),
            trim((string) ($filters['status'] ?? '')),
            trim((string) ($filters['role'] ?? '')),
            max(1, (int) ($filters['page'] ?? 1)),
            25,
            $visibilityRole
        );
        $actorId = Auth::id() ?? 0;
        foreach ($result['items'] as &$item) {
            $item['can_manage'] = $this->canManage((int) $item['id'], $actorId);
        }
        unset($item);
        return $result;
    }

    public function roles(): array
    {
        $roles = $this->users->roles();
        if (Auth::hasRole('SUPER_ADMIN')) return $roles;
        return array_values(array_filter($roles, static fn(array $role): bool => $role['name'] === 'THERAPIST'));
    }

    public function canManage(int $id, int $actorId): bool
    {
        $user = $this->users->find($id);
        if ($user === null) return false;
        if (Auth::hasRole('SUPER_ADMIN')) return true;
        return Auth::hasRole('ADMIN') && (int) $user['id'] !== $actorId && !$this->users->hasRole($id, 'ADMIN') && !$this->users->hasRole($id, 'SUPER_ADMIN');
    }

    public function find(int $id): array
    {
        $user = $this->users->find($id);
        if ($user === null) throw new RuntimeException('User not found.', 404);
        return $user;
    }

    public function create(string $name, string $email, string $phone, string $password, string $status, array $roleIds, int $actorId): int
    {
        $this->validate($name, $email, $password, true);
        $this->assertRoleSelection($roleIds, $actorId);
        if ($this->users->findByEmail($email) !== null) throw new InvalidArgumentException('An account with that email already exists.');
        $pdo = Database::connection();
        $pdo->beginTransaction();
        try {
            $id = $this->users->create([
                'full_name' => trim($name), 'email' => strtolower(trim($email)), 'phone' => trim($phone) ?: null,
                'password_hash' => password_hash($password, PASSWORD_DEFAULT), 'status' => $status === 'inactive' ? 'inactive' : 'active',
            ]);
            $this->users->assignRoles($id, $roleIds);
            $this->auditSafely($actorId, 'create', $id, ['email' => strtolower(trim($email))]);
            $pdo->commit();
            return $id;
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            throw $e;
        }
    }

    public function update(int $id, string $name, string $email, string $phone, string $status, array $roleIds, ?string $newPassword, int $actorId): void
    {
        $existing = $this->find($id);
        $this->assertCanManageTarget($existing, $actorId);
        $this->validate($name, $email, $newPassword, $newPassword !== null && $newPassword !== '');
        $this->assertRoleSelection($roleIds, $actorId);
        $email = strtolower(trim($email));
        $byEmail = $this->users->findByEmail($email);
        if ($byEmail !== null && (int) $byEmail['id'] !== $id) throw new InvalidArgumentException('An account with that email already exists.');

        if ($id === $actorId && $status !== 'active') throw new InvalidArgumentException('You cannot disable your own account.');
        $hadSuper = $this->users->hasRole($id, 'SUPER_ADMIN');
        $willSuper = $this->roleIdsContain($roleIds, 'SUPER_ADMIN');
        if ($hadSuper && (!$willSuper || $status !== 'active') && $this->users->countActiveSuperAdmins() <= 1) {
            throw new InvalidArgumentException('The last active Super Admin cannot be removed or disabled.');
        }

        $pdo = Database::connection();
        $pdo->beginTransaction();
        try {
            $data = ['full_name' => trim($name), 'email' => $email, 'phone' => trim($phone) ?: null, 'status' => $status === 'inactive' ? 'inactive' : 'active'];
            if ($newPassword !== null && $newPassword !== '') $data['password_hash'] = password_hash($newPassword, PASSWORD_DEFAULT);
            $this->users->update($id, $data);
            $this->users->assignRoles($id, $roleIds);
            $this->auditSafely($actorId, 'update', $id, ['email' => $email]);
            $pdo->commit();
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            throw $e;
        }
    }

    public function disable(int $id, int $actorId): void
    {
        $user = $this->find($id);
        $this->assertCanManageTarget($user, $actorId);
        if ($id === $actorId) throw new InvalidArgumentException('You cannot disable your own account.');
        if ($user['status'] === 'inactive') return;
        if ($this->users->hasRole($id, 'SUPER_ADMIN') && $this->users->countActiveSuperAdmins() <= 1) throw new InvalidArgumentException('The last active Super Admin cannot be disabled.');
        $this->users->update($id, ['status' => 'inactive']);
        $this->auditSafely($actorId, 'disable', $id);
    }

    public function profile(int $id): array { return $this->find($id); }

    public function updateEmail(int $id, string $email, string $currentPassword): void
    {
        $user = $this->users->findByEmail($email);
        if ($user !== null && (int) $user['id'] !== $id) throw new InvalidArgumentException('That email address is already in use.');
        $current = $this->find($id);
        $full = $this->users->findByEmail((string) $current['email']);
        if (!is_array($full) || !password_verify($currentPassword, (string) $full['password_hash'])) throw new InvalidArgumentException('Current password is incorrect.');
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) throw new InvalidArgumentException('Enter a valid email address.');
        $this->users->update($id, ['email' => strtolower(trim($email))]);
        $this->auditSafely($id, 'change_email', $id);
    }

    public function updatePassword(int $id, string $currentPassword, string $newPassword, string $confirmation): void
    {
        if (strlen($newPassword) < 12 || !hash_equals($newPassword, $confirmation)) throw new InvalidArgumentException('New password must be at least 12 characters and both fields must match.');
        $current = $this->find($id);
        $full = $this->users->findByEmail((string) $current['email']);
        if (!is_array($full) || !password_verify($currentPassword, (string) $full['password_hash'])) throw new InvalidArgumentException('Current password is incorrect.');
        $this->users->update($id, ['password_hash' => password_hash($newPassword, PASSWORD_DEFAULT)]);
        $this->auditSafely($id, 'change_password', $id);
    }

    private function validate(string $name, string $email, ?string $password, bool $passwordRequired): void
    {
        if (trim($name) === '' || mb_strlen(trim($name)) > 150) throw new InvalidArgumentException('Enter a valid full name.');
        if (!filter_var(trim($email), FILTER_VALIDATE_EMAIL)) throw new InvalidArgumentException('Enter a valid email address.');
        if ($passwordRequired && ($password === null || strlen($password) < 12)) throw new InvalidArgumentException('Password must be at least 12 characters.');
    }

    private function assertRoleSelection(array $roleIds, int $actorId): void
    {
        if (!$roleIds) throw new InvalidArgumentException('Select at least one role.');
        $roles = $this->users->roles();
        $valid = array_map(static fn(array $r): int => (int) $r['id'], $roles);
        foreach ($roleIds as $roleId) if (!in_array((int) $roleId, $valid, true)) throw new InvalidArgumentException('One or more selected roles are invalid.');

        if (!Auth::hasRole('SUPER_ADMIN')) {
            if ($this->roleIdsContain($roleIds, 'SUPER_ADMIN') || $this->roleIdsContain($roleIds, 'ADMIN')) {
                throw new InvalidArgumentException('Only a Super Admin can assign Administrator or Super Admin roles.');
            }
        }
    }

    private function assertCanManageTarget(array $user, int $actorId): void
    {
        if (Auth::hasRole('SUPER_ADMIN')) return;
        if (!Auth::hasRole('ADMIN')) throw new InvalidArgumentException('You do not have permission to manage user accounts.');
        if ((int) $user['id'] === $actorId) throw new InvalidArgumentException('Use My Profile to manage your own account.');
        if ($this->users->hasRole((int) $user['id'], 'ADMIN') || $this->users->hasRole((int) $user['id'], 'SUPER_ADMIN')) {
            throw new InvalidArgumentException('Administrators cannot manage Administrator or Super Admin accounts.');
        }
    }

    private function roleIdsContain(array $roleIds, string $roleName): bool
    {
        foreach ($this->users->roles() as $role) if ((int) $role['id'] && in_array((int) $role['id'], array_map('intval', $roleIds), true) && $role['name'] === $roleName) return true;
        return false;
    }

    private function auditSafely(int $actorId, string $action, int $entityId, array $metadata = []): void
    {
        try { $this->users->audit($actorId, $action, $entityId, $metadata); } catch (\Throwable) {}
    }
}
