<?php

namespace App\Services;



/**
 * Servicio de Autenticación
 * Maneja login, logout y gestión de sesiones de administradores
 */
class AuthService
{
    private Database $db;
    private Logger $logger;
    private SecurityService $security;

    public function __construct()
    {
        $this->db = Database::getInstance();
        $this->logger = new Logger();
        $this->security = new SecurityService();
    }

    // ========================================================================
    // AUTENTICACIÓN
    // ========================================================================

    /**
     * Login de administrador
     */
    public function login(string $email, string $password): array
    {
        $email = $this->security->sanitizeEmail($email);

        // Verificar rate limiting
        if (!$this->security->checkRateLimit('admin_login', 5, 15)) {
            $this->logger->logSecurity(
                'login_rate_limit',
                "Login rate limit exceeded for: {$email}",
                'warning'
            );

            return [
                'success' => false,
                'message' => 'Demasiados intentos fallidos. Intente nuevamente en 15 minutos.'
            ];
        }

        // Buscar admin
        $admin = $this->db->fetchOne(
            "SELECT * FROM admins WHERE email = :email AND activo = 1",
            [':email' => $email]
        );

        if (!$admin) {
            $this->security->recordAttempt('admin_login');
            $this->logger->logSecurity('login_failed', "Login failed - user not found: {$email}");

            return [
                'success' => false,
                'message' => 'Credenciales incorrectas.'
            ];
        }

        // Verificar contraseña
        if (!$this->security->verifyPassword($password, $admin['password'])) {
            $this->security->recordAttempt('admin_login');
            $this->logger->logSecurity('login_failed', "Login failed - wrong password: {$email}");

            return [
                'success' => false,
                'message' => 'Credenciales incorrectas.'
            ];
        }

        // Crear sesión
        $sessionId = $this->createSession($admin['id']);

        // Actualizar último login
        $this->db->update(
            'admins',
            ['ultimo_login' => gmdate('Y-m-d H:i:s')],
            'id = :id',
            [':id' => $admin['id']]
        );

        $this->logger->logAdminAccess(
            $admin['id'],
            'login',
            "Admin logged in successfully",
            ['email' => $email]
        );

        return [
            'success' => true,
            'message' => 'Login exitoso',
            'admin' => [
                'id'     => $admin['id'],
                'nombre' => $admin['nombre'],
                'email'  => $admin['email'],
                'rol'    => $admin['rol'] ?? 'asesor'
            ]
        ];
    }

    /**
     * Logout
     */
    public function logout(): void
    {
        if (isset($_SESSION['admin_id'])) {
            $adminId = $_SESSION['admin_id'];

            // Eliminar sesión de la base de datos
            if (isset($_SESSION['session_id'])) {
                $this->db->delete(
                    'sessions',
                    'id = :id',
                    [':id' => $_SESSION['session_id']]
                );
            }

            $this->logger->logAdminAccess($adminId, 'logout', 'Admin logged out');
        }

        // Limpiar sesión
        $_SESSION = [];

        // Destruir cookie de sesión
        if (ini_get('session.use_cookies')) {
            $params = session_get_cookie_params();
            setcookie(
                session_name(),
                '',
                time() - 42000,
                $params['path'],
                $params['domain'],
                $params['secure'],
                $params['httponly']
            );
        }

        session_destroy();
    }

    /**
     * Crear sesión
     */
    private function createSession(int $adminId): string
    {
        // Usar token como identificador estable (no session_id que puede cambiar)
        $token = $this->security->generateToken(64);
        $dbSessionId = bin2hex(random_bytes(16)); // ID estable para la BD

        // Calcular expiración en UTC para que coincida con NOW() de MySQL
        $lifetime = (int) ($_ENV['SESSION_LIFETIME'] ?? 7200);
        $expiresAt = gmdate('Y-m-d H:i:s', time() + $lifetime);

        // Guardar sesión en base de datos
        $this->db->insert('sessions', [
            'id' => $dbSessionId,
            'admin_id' => $adminId,
            'token' => $token,
            'ip_address' => $this->security->getClientIP(),
            'user_agent' => $this->security->getUserAgent(),
            'expires_at' => $expiresAt
        ]);

        // Guardar en sesión PHP — usamos db_session_id separado del session_id de PHP
        $_SESSION['admin_id'] = $adminId;
        $_SESSION['session_id'] = $dbSessionId;
        $_SESSION['session_token'] = $token;
        $_SESSION['expires_at'] = $expiresAt;

        return $dbSessionId;
    }

    /**
     * Verificar si hay sesión activa
     */
    public function check(): bool
    {
        if (!isset($_SESSION['admin_id'], $_SESSION['session_token'])) {
            return false;
        }

        // Verificar sesión en base de datos
        // Usar UTC_TIMESTAMP() para ser consistente con gmdate()
        $session = $this->db->fetchOne(
            "SELECT s.*, a.activo FROM sessions s
             INNER JOIN admins a ON s.admin_id = a.id
             WHERE s.id = :id AND s.token = :token AND s.expires_at > UTC_TIMESTAMP()",
            [
                ':id' => $_SESSION['session_id'],
                ':token' => $_SESSION['session_token']
            ]
        );

        if (!$session || $session['activo'] != 1) {
            $this->logout();
            return false;
        }

        // Actualizar actividad
        $this->db->update(
            'sessions',
            ['last_activity' => gmdate('Y-m-d H:i:s')],
            'id = :id',
            [':id' => $_SESSION['session_id']]
        );

        return true;
    }

    /**
     * Obtener admin autenticado
     */
    public function user(): ?array
    {
        if (!$this->check()) {
            return null;
        }

        $admin = $this->db->fetchOne(
            "SELECT id, email, nombre, rol, created_at FROM admins WHERE id = :id AND activo = 1",
            [':id' => $_SESSION['admin_id']]
        );

        if (!$admin) {
            $this->logout();
            return null;
        }

        // Actualizar rol en sesión si cambió en BD (seguridad extra)
        if (isset($admin['rol']) && (!isset($_SESSION['admin_rol']) || $_SESSION['admin_rol'] !== $admin['rol'])) {
            $_SESSION['admin_rol'] = $admin['rol'];
            $this->logger->logAdminAccess(
                $admin['id'],
                'role_sync',
                "Role synchronized from database: {$admin['rol']}"
            );
        }

        return $admin;
    }

    /**
     * Requerir autenticación - lanza excepción si no hay sesión válida
     * Usar en páginas administrativas para forzar login
     */
    public function requireAuth(): array
    {
        $user = $this->user();

        if (!$user) {
            // Registrar intento de acceso no autorizado
            $this->logger->logSecurity(
                'unauthorized_access',
                'Attempted access without valid session',
                'warning'
            );

            // Redirigir al login
            header('Location: ' . base_url('admin/login'));
            exit;
        }

        return $user;
    }

    /**
     * Verificar si el usuario está autenticado (alias de check)
     */
    public function isAuthenticated(): bool
    {
        return $this->check();
    }

    /**
     * Obtener ID del admin autenticado
     */
    public function id(): ?int
    {
        return $_SESSION['admin_id'] ?? null;
    }

    // ========================================================================
    // GESTIÓN DE CONTRASEÑAS
    // ========================================================================

    /**
     * Cambiar contraseña
     */
    public function changePassword(int $adminId, string $currentPassword, string $newPassword): array
    {
        // Validar nueva contraseña
        if (strlen($newPassword) < 8) {
            return [
                'success' => false,
                'message' => 'La contraseña debe tener al menos 8 caracteres.'
            ];
        }

        // Obtener admin
        $admin = $this->db->fetchOne(
            "SELECT * FROM admins WHERE id = :id",
            [':id' => $adminId]
        );

        if (!$admin) {
            return [
                'success' => false,
                'message' => 'Administrador no encontrado.'
            ];
        }

        // Verificar contraseña actual
        if (!$this->security->verifyPassword($currentPassword, $admin['password'])) {
            $this->logger->logSecurity(
                'password_change_failed',
                "Failed password change - wrong current password",
                'warning',
                ['admin_id' => $adminId]
            );

            return [
                'success' => false,
                'message' => 'La contraseña actual es incorrecta.'
            ];
        }

        // Actualizar contraseña
        $newHash = $this->security->hashPassword($newPassword);
        $this->db->update(
            'admins',
            ['password' => $newHash],
            'id = :id',
            [':id' => $adminId]
        );

        // Cerrar todas las sesiones del admin
        $this->db->delete('sessions', 'admin_id = :id', [':id' => $adminId]);

        $this->logger->logAdminAccess(
            $adminId,
            'password_changed',
            'Password changed successfully'
        );

        return [
            'success' => true,
            'message' => 'Contraseña actualizada correctamente. Por favor inicie sesión nuevamente.'
        ];
    }

    /**
     * Solicitar reset de contraseña
     */
    public function requestPasswordReset(string $email): array
    {
        $email = $this->security->sanitizeEmail($email);

        // Verificar rate limiting
        if (!$this->security->checkRateLimit('password_reset', 3, 60)) {
            return [
                'success' => false,
                'message' => 'Demasiadas solicitudes. Intente nuevamente en 1 hora.'
            ];
        }

        // Buscar admin
        $admin = $this->db->fetchOne(
            "SELECT * FROM admins WHERE email = :email AND activo = 1",
            [':email' => $email]
        );

        // Siempre retornar éxito (seguridad)
        if (!$admin) {
            $this->security->recordAttempt('password_reset');
            return [
                'success' => true,
                'message' => 'Si el email existe, recibirá un enlace de recuperación.'
            ];
        }

        // Generar token de reset
        $token = $this->security->generateToken(64);
        $expiresAt = gmdate('Y-m-d H:i:s', time() + 3600);

        $this->db->insert('password_resets', [
            'admin_id' => $admin['id'],
            'token' => $token,
            'expires_at' => $expiresAt
        ]);

        // TODO: Enviar email con link de reset
        // $emailService->sendPasswordReset($admin['email'], $token);

        $this->logger->logAdminAccess(
            $admin['id'],
            'password_reset_requested',
            'Password reset token generated'
        );

        return [
            'success' => true,
            'message' => 'Si el email existe, recibirá un enlace de recuperación.',
            'token' => $token // Solo para desarrollo
        ];
    }

    /**
     * Resetear contraseña con token
     */
    public function resetPassword(string $token, string $newPassword): array
    {
        // Validar nueva contraseña
        if (strlen($newPassword) < 8) {
            return [
                'success' => false,
                'message' => 'La contraseña debe tener al menos 8 caracteres.'
            ];
        }

        // Buscar token válido
        $reset = $this->db->fetchOne(
            "SELECT * FROM password_resets
             WHERE token = :token AND usado = 0 AND expires_at > UTC_TIMESTAMP()",
            [':token' => $token]
        );

        if (!$reset) {
            return [
                'success' => false,
                'message' => 'El enlace de recuperación es inválido o ha expirado.'
            ];
        }

        // Actualizar contraseña
        $newHash = $this->security->hashPassword($newPassword);
        $this->db->update(
            'admins',
            ['password' => $newHash],
            'id = :id',
            [':id' => $reset['admin_id']]
        );

        // Marcar token como usado
        $this->db->update(
            'password_resets',
            ['usado' => 1],
            'id = :id',
            [':id' => $reset['id']]
        );

        // Cerrar todas las sesiones
        $this->db->delete('sessions', 'admin_id = :id', [':id' => $reset['admin_id']]);

        $this->logger->logAdminAccess(
            $reset['admin_id'],
            'password_reset_completed',
            'Password reset successfully'
        );

        return [
            'success' => true,
            'message' => 'Contraseña restablecida correctamente. Puede iniciar sesión.'
        ];
    }

    // ========================================================================
    // LIMPIEZA
    // ========================================================================

    /**
     * Limpiar sesiones expiradas
     */
    public function cleanExpiredSessions(): int
    {
        $deleted = $this->db->delete('sessions', 'expires_at < UTC_TIMESTAMP()');

        if ($deleted > 0) {
            $this->logger->info("Cleaned {$deleted} expired sessions");
        }

        return $deleted;
    }

    /**
     * Limpiar tokens de reset expirados
     */
    public function cleanExpiredResetTokens(): int
    {
        $deleted = $this->db->delete('password_resets', 'expires_at < UTC_TIMESTAMP()');

        if ($deleted > 0) {
            $this->logger->info("Cleaned {$deleted} expired reset tokens");
        }

        return $deleted;
    }
}
