<?php

namespace App\Core;

use PDO;

class Auth
{
    /**
     * Attempt login by checking superadmins, then managers, then employees.
     * Returns array with user_type/user_id/name/role info on success, or null.
     */
    public static function attempt(string $username, string $password, string $ip = ''): ?array
    {
        $db = Database::connection();

        $checks = [
            ['table' => 'superadmins', 'type' => 'superadmin', 'userCol' => 'username'],
            ['table' => 'managers',    'type' => 'manager',    'userCol' => 'username'],
            ['table' => 'employees',   'type' => 'employee',   'userCol' => 'username'],
        ];

        foreach ($checks as $c) {
            $stmt = $db->prepare("SELECT * FROM `{$c['table']}` WHERE `{$c['userCol']}` = :u1 OR email = :u2 LIMIT 1");
            $stmt->execute(['u1' => $username, 'u2' => $username]);
            $row = $stmt->fetch();

            if (!$row) continue;

            if (isset($row['is_active']) && (int)$row['is_active'] === 0) {
                AuditLog::record($c['type'], (int)$row['id'], $row['name'] ?? $row['username'] ?? '', 'user', (string)$row['id'], 'login_failed', null, null, $ip);
                return ['error' => 'disabled'];
            }

            if (!empty($row['password_hash']) && password_verify($password, $row['password_hash'])) {

                if (!empty($row['two_factor_enabled']) && !empty($row['two_factor_secret'])) {
                    // Don't fully log in yet - stash a pending state and require OTP verification.
                    session_regenerate_id(true);
                    $_SESSION['pending_2fa_type'] = $c['type'];
                    $_SESSION['pending_2fa_id']   = (int)$row['id'];
                    $_SESSION['pending_2fa_ip']   = $ip;
                    return ['requires_2fa' => true];
                }

                self::startSession($c['type'], (int)$row['id'], $ip);

                if (!empty($row['force_password_change'])) {
                    $_SESSION['must_change_password'] = true;
                }

                if ($c['type'] !== 'superadmin') {
                    $db->prepare("UPDATE `{$c['table']}` SET last_login = NOW() WHERE id = :id")
                       ->execute(['id' => $row['id']]);
                }

                AuditLog::record($c['type'], (int)$row['id'], $row['name'] ?? $row['username'] ?? '', 'user', (string)$row['id'], 'login', null, null, $ip);

                return self::currentUser();
            }
        }

        AuditLog::record('unknown', 0, $username, 'user', '', 'login_failed', null, null, $ip);
        return null;
    }

    /**
     * Complete a login that was paused for 2FA verification.
     */
    public static function verifyTwoFactorAndLogin(string $code): ?array
    {
        if (!isset($_SESSION['pending_2fa_type'], $_SESSION['pending_2fa_id'])) {
            return null;
        }

        $type = $_SESSION['pending_2fa_type'];
        $id   = (int)$_SESSION['pending_2fa_id'];
        $ip   = $_SESSION['pending_2fa_ip'] ?? '';

        $db = Database::connection();
        $table = $type === 'superadmin' ? 'superadmins' : ($type === 'manager' ? 'managers' : 'employees');
        $stmt = $db->prepare("SELECT * FROM `{$table}` WHERE id = :id LIMIT 1");
        $stmt->execute(['id' => $id]);
        $row = $stmt->fetch();

        if (!$row || empty($row['two_factor_secret']) || !Totp::verify($row['two_factor_secret'], $code)) {
            return null;
        }

        unset($_SESSION['pending_2fa_type'], $_SESSION['pending_2fa_id'], $_SESSION['pending_2fa_ip']);
        self::startSession($type, $id, $ip);

        if (!empty($row['force_password_change'])) {
            $_SESSION['must_change_password'] = true;
        }

        if ($type !== 'superadmin') {
            $db->prepare("UPDATE `{$table}` SET last_login = NOW() WHERE id = :id")->execute(['id' => $id]);
        }

        AuditLog::record($type, $id, $row['name'] ?? $row['username'] ?? '', 'user', (string)$id, 'login', null, null, $ip);

        return self::currentUser();
    }

    public static function hasPending2fa(): bool
    {
        return isset($_SESSION['pending_2fa_type'], $_SESSION['pending_2fa_id']);
    }

    private static function startSession(string $userType, int $userId, string $ip): void
    {
        session_regenerate_id(true);
        $_SESSION['user_type'] = $userType;
        $_SESSION['user_id']   = $userId;
        $_SESSION['ip']        = $ip;
        $_SESSION['last_activity'] = time();
    }

    // ── Dev Admin: View-as-User (impersonation) ─────────────────────────────

    /**
     * Start impersonating a target user.
     * Saves the real dev admin session, then loads the target user into the
     * active session. Nothing is written to the database; the target user's
     * account, password, and data are never touched.
     * Only callable by a logged-in is_dev_admin super admin.
     */
    public static function impersonateStart(string $targetType, int $targetId): bool
    {
        $real = self::currentUser();
        if (!$real || empty($real['is_super_admin'])) return false;

        $_SESSION['dev_impersonate'] = [
            'real_user_type' => $_SESSION['user_type'],
            'real_user_id'   => $_SESSION['user_id'],
            'real_name'      => $real['name'],
        ];
        $_SESSION['user_type'] = $targetType;
        $_SESSION['user_id']   = $targetId;
        return true;
    }

    /** Stop impersonating — restore the dev admin's real session. */
    public static function impersonateStop(): void
    {
        if (!isset($_SESSION['dev_impersonate'])) return;
        $saved = $_SESSION['dev_impersonate'];
        $_SESSION['user_type'] = $saved['real_user_type'];
        $_SESSION['user_id']   = $saved['real_user_id'];
        unset($_SESSION['dev_impersonate']);
    }

    /** Is the current request running as an impersonated user? */
    public static function isImpersonating(): bool
    {
        return isset($_SESSION['dev_impersonate']);
    }

    /** Return the saved real-admin info during impersonation. */
    public static function impersonatorInfo(): ?array
    {
        return $_SESSION['dev_impersonate'] ?? null;
    }

    public static function check(): bool
    {
        return isset($_SESSION['user_type'], $_SESSION['user_id']);
    }

    public static function logout(): void
    {
        $user = self::currentUser();
        if ($user) {
            AuditLog::record($user['user_type'], $user['user_id'], $user['name'], 'user', (string)$user['user_id'], 'logout');
        }
        $_SESSION = [];
        session_destroy();
    }

    /**
     * Load the full current user record: identity + role(s) + department/project scope.
     */
    public static function currentUser(): ?array
    {
        if (!self::check()) return null;

        $type = $_SESSION['user_type'];
        $id   = $_SESSION['user_id'];
        $db   = Database::connection();

        $table = $type === 'superadmin' ? 'superadmins' : ($type === 'manager' ? 'managers' : 'employees');
        $stmt = $db->prepare("SELECT * FROM `{$table}` WHERE id = :id LIMIT 1");
        $stmt->execute(['id' => $id]);
        $row = $stmt->fetch();
        if (!$row) return null;

        $roles = [];
        if ($type === 'superadmin') {
            $roles = [['code' => 'super_admin', 'name' => 'Super Admin', 'level' => 100]];
        } else {
            $stmt = $db->prepare(
                "SELECT r.code, r.name, r.level FROM user_roles ur
                 JOIN roles r ON r.id = ur.role_id
                 WHERE ur.user_type = :t AND ur.user_id = :id"
            );
            $stmt->execute(['t' => $type, 'id' => $id]);
            $roles = $stmt->fetchAll();
        }

        return [
            'user_type' => $type,
            'user_id'   => (int)$id,
            'name'      => $row['name'] ?? $row['full_name'] ?? $row['username'] ?? 'User',
            'username'  => $row['username'] ?? null,
            'email'     => $row['email'] ?? null,
            'roles'     => $roles,
            'is_super_admin' => $type === 'superadmin',
            'is_dev_admin'   => $type === 'superadmin' && !empty($row['is_dev_admin']),
            'raw'       => $row,
        ];
    }
}
