<?php

namespace App\Models;

use App\Core\Database;
use PDO;

class UserManager
{
    /**
     * Unified list of all users (superadmins, managers, employees) for the admin panel.
     * Super admins are shown but not editable/deletable via this panel.
     */
    public static function all(string $search = ''): array
    {
        $db = Database::connection();
        $like = '%' . $search . '%';

        $sql = "
            SELECT id, 'superadmin' AS user_type, full_name AS name, username, email,
                   1 AS is_active, NULL AS last_login
            FROM superadmins
            WHERE (:s1 = '' OR full_name LIKE :s1a OR username LIKE :s1b OR email LIKE :s1c)

            UNION ALL

            SELECT id, 'manager' AS user_type, name, username, email,
                   COALESCE(is_active,1) AS is_active, last_login
            FROM managers
            WHERE (:s2 = '' OR name LIKE :s2a OR username LIKE :s2b OR email LIKE :s2c)
        ";

        $params = [
            's1' => $search, 's1a' => $like, 's1b' => $like, 's1c' => $like,
            's2' => $search, 's2a' => $like, 's2b' => $like, 's2c' => $like,
        ];

        // Employees (security guards) stay out of this list entirely while onboarding
        // is paused - controlled by the one central flag in App\Core\Features.
        if (\App\Core\Features::employeeOnboardingEnabled()) {
            $sql .= "
                UNION ALL

                SELECT id, 'employee' AS user_type, name, username, email,
                       COALESCE(is_active,1) AS is_active, last_login
                FROM employees
                WHERE username IS NOT NULL
                  AND (:s3 = '' OR name LIKE :s3a OR username LIKE :s3b OR email LIKE :s3c)
            ";
            $params += ['s3' => $search, 's3a' => $like, 's3b' => $like, 's3c' => $like];
        }

        $sql .= " ORDER BY user_type ASC, name ASC";

        $stmt = $db->prepare($sql);
        $stmt->execute($params);

        $rows = $stmt->fetchAll();

        // Attach role, department, project summaries for managers/employees
        foreach ($rows as &$row) {
            if ($row['user_type'] === 'superadmin') {
                $row['role_name'] = 'Super Admin';
                $row['departments'] = 'All Departments';
                $row['projects'] = 'All Projects';
                $row['reports_to_name'] = '—';
                continue;
            }
            $row['role_name']   = self::roleNameFor($row['user_type'], (int)$row['id']);
            $row['departments'] = self::departmentSummary($row['user_type'], (int)$row['id']);
            $row['projects']    = self::projectSummary($row['user_type'], (int)$row['id']);
            $row['reports_to_name'] = self::reportsToLabel($row['user_type'], (int)$row['id']);
        }

        return $rows;
    }

    public static function roleNameFor(string $userType, int $userId): string
    {
        $db = Database::connection();
        $stmt = $db->prepare(
            "SELECT r.name FROM user_roles ur JOIN roles r ON r.id = ur.role_id
             WHERE ur.user_type = :t AND ur.user_id = :id LIMIT 1"
        );
        $stmt->execute(['t' => $userType, 'id' => $userId]);
        $r = $stmt->fetch();
        return $r ? $r['name'] : '—';
    }

    public static function departmentSummary(string $userType, int $userId): string
    {
        $db = Database::connection();
        $stmt = $db->prepare(
            "SELECT d.name FROM user_departments ud JOIN departments d ON d.id = ud.department_id
             WHERE ud.user_type = :t AND ud.user_id = :id"
        );
        $stmt->execute(['t' => $userType, 'id' => $userId]);
        $names = array_column($stmt->fetchAll(), 'name');
        if (empty($names)) return 'None';
        return count($names) > 2 ? ($names[0] . ', ' . $names[1] . ' +' . (count($names) - 2)) : implode(', ', $names);
    }

    public static function projectSummary(string $userType, int $userId): string
    {
        $db = Database::connection();
        $stmt = $db->prepare(
            "SELECT COUNT(*) c FROM user_projects WHERE user_type = :t AND user_id = :id"
        );
        $stmt->execute(['t' => $userType, 'id' => $userId]);
        $c = (int)$stmt->fetch()['c'];
        return $c . ' Project' . ($c === 1 ? '' : 's');
    }

    public static function find(string $userType, int $id): ?array
    {
        $db = Database::connection();
        $table = $userType === 'superadmin' ? 'superadmins' : ($userType === 'manager' ? 'managers' : 'employees');
        $stmt = $db->prepare("SELECT * FROM `{$table}` WHERE id = :id");
        $stmt->execute(['id' => $id]);
        $row = $stmt->fetch();
        return $row ?: null;
    }

    /**
     * Grant workspace login access to an EXISTING imported manager or employee record
     * (their name/dept/email were already imported from Google Sheets — this just
     * turns on login for them). Does not create new manager/employee rows.
     */
    /**
     * Creates a brand-new manager/employee record (for someone not yet on
     * file / not yet in the staffing import), then hands back their new id
     * so the caller can immediately call grantAccess() on it exactly as it
     * would for any already-imported person. This is intentionally a
     * separate method from grantAccess() above — it only ever INSERTs a
     * new row, so it can never touch or overwrite any existing manager or
     * employee record.
     */
    public static function createNewPerson(string $userType, string $name): int
    {
        if (!in_array($userType, ['manager', 'employee'], true)) {
            throw new \InvalidArgumentException('Can only create a new manager or employee record.');
        }
        $name = trim($name);
        if ($name === '') {
            throw new \InvalidArgumentException('Name is required.');
        }

        $db = Database::connection();
        if ($userType === 'manager') {
            $stmt = $db->prepare("INSERT INTO managers (name, source) VALUES (:name, 'custom')");
        } else {
            $stmt = $db->prepare("INSERT INTO employees (name, source) VALUES (:name, 'custom')");
        }
        $stmt->execute(['name' => $name]);
        return (int)$db->lastInsertId();
    }

    public static function grantAccess(string $userType, int $existingId, array $data): array
    {
        if (!in_array($userType, ['manager', 'employee'], true)) {
            throw new \InvalidArgumentException('Access can only be granted to an existing manager or employee.');
        }
        if ($userType === 'employee' && !\App\Core\Features::employeeOnboardingEnabled()) {
            throw new \RuntimeException('Employee onboarding is currently paused.');
        }

        $db = Database::connection();
        $table = $userType === 'manager' ? 'managers' : 'employees';

        // Confirm the record exists and doesn't already have access
        $check = $db->prepare("SELECT id FROM `{$table}` WHERE id = :id");
        $check->execute(['id' => $existingId]);
        if (!$check->fetch()) {
            throw new \RuntimeException('Selected person was not found.');
        }

        $personName = self::nameFor($userType, $existingId);
        $username = ($data['username'] ?? '') ?: self::generateUsername($personName);
        $password = ($data['password'] ?? '') ?: self::generateTempPassword($personName);
        $hash = password_hash($password, PASSWORD_DEFAULT);

        $stmt = $db->prepare(
            "UPDATE `{$table}` SET username = :username, email = COALESCE(email, :email), password_hash = :password_hash, is_active = 1, force_password_change = 1 WHERE id = :id"
        );
        $stmt->execute([
            'username'      => $username,
            'email'         => $data['email'] ?? null,
            'password_hash' => $hash,
            'id'            => $existingId,
        ]);

        if (!empty($data['role_id'])) {
            self::setRole($userType, $existingId, (int)$data['role_id']);
        }
        if (!empty($data['department_ids'])) {
            self::setDepartments($userType, $existingId, $data['department_ids']);
        }
        if (!empty($data['project_ids'])) {
            self::setProjects($userType, $existingId, $data['project_ids']);
        }

        return ['username' => $username, 'password' => $password];
    }

    public static function generateUsername(string $fullName): string
    {
        $parts = preg_split('/\s+/', trim($fullName));
        $first = strtolower(preg_replace('/[^a-z]/i', '', $parts[0] ?? 'user'));
        $last  = strtolower(preg_replace('/[^a-z]/i', '', $parts[count($parts) - 1] ?? ''));
        $base  = $last && $last !== $first ? "{$first}.{$last}" : $first;
        return $base ?: 'user' . random_int(100, 999);
    }

    public static function generateTempPassword(string $fullName): string
    {
        $parts = preg_split('/\s+/', trim($fullName));
        $first = preg_replace('/[^a-zA-Z]/', '', $parts[0] ?? 'User');
        $first = $first !== '' ? ucfirst(strtolower($first)) : 'User';
        return "{$first}@" . random_int(1000, 9999);
    }

    /** Super Admin sets a new password for a user directly - no email, immediate effect, forces change on next login. */
    public static function adminResetPassword(string $userType, int $userId, string $newPassword): void
    {
        if (!in_array($userType, ['manager', 'employee'], true)) {
            throw new \InvalidArgumentException('Password reset is only available for managers and employees here.');
        }
        $table = $userType === 'manager' ? 'managers' : 'employees';
        $db = Database::connection();
        $db->prepare("UPDATE `{$table}` SET password_hash = :p, force_password_change = 1 WHERE id = :id")
           ->execute(['p' => password_hash($newPassword, PASSWORD_DEFAULT), 'id' => $userId]);
    }

    public static function usernameFor(string $userType, int $userId): ?string
    {
        $db = Database::connection();
        $table = $userType === 'superadmin' ? 'superadmins' : ($userType === 'manager' ? 'managers' : 'employees');
        $stmt = $db->prepare("SELECT username FROM `{$table}` WHERE id = :id");
        $stmt->execute(['id' => $userId]);
        $r = $stmt->fetch();
        return $r ? $r['username'] : null;
    }
    public static function toggleActive(string $userType, int $id, bool $active): void
    {
        if ($userType === 'superadmin') return; // cannot disable super admins here
        $db = Database::connection();
        $table = $userType === 'manager' ? 'managers' : 'employees';
        $db->prepare("UPDATE `{$table}` SET is_active = :a WHERE id = :id")
           ->execute(['a' => $active ? 1 : 0, 'id' => $id]);
    }

    public static function setRole(string $userType, int $userId, int $roleId): void
    {
        $db = Database::connection();
        $db->prepare("DELETE FROM user_roles WHERE user_type = :t AND user_id = :id")
           ->execute(['t' => $userType, 'id' => $userId]);
        $db->prepare("INSERT INTO user_roles (user_type, user_id, role_id) VALUES (:t, :id, :r)")
           ->execute(['t' => $userType, 'id' => $userId, 'r' => $roleId]);
    }

    public static function setDepartments(string $userType, int $userId, array $departmentIds): void
    {
        $db = Database::connection();
        $db->prepare("DELETE FROM user_departments WHERE user_type = :t AND user_id = :id")
           ->execute(['t' => $userType, 'id' => $userId]);

        $stmt = $db->prepare(
            "INSERT INTO user_departments (user_type, user_id, department_id, is_primary) VALUES (:t, :id, :d, :p)"
        );
        foreach ($departmentIds as $i => $deptId) {
            $stmt->execute(['t' => $userType, 'id' => $userId, 'd' => (int)$deptId, 'p' => $i === 0 ? 1 : 0]);
        }
    }

    public static function setProjects(string $userType, int $userId, array $projectIds): void
    {
        $db = Database::connection();
        $db->prepare("DELETE FROM user_projects WHERE user_type = :t AND user_id = :id")
           ->execute(['t' => $userType, 'id' => $userId]);

        $stmt = $db->prepare(
            "INSERT INTO user_projects (user_type, user_id, project_id, access_level) VALUES (:t, :id, :p, 'contribute')"
        );
        foreach ($projectIds as $projId) {
            $stmt->execute(['t' => $userType, 'id' => $userId, 'p' => (int)$projId]);
        }
    }

    public static function currentDepartmentIds(string $userType, int $userId): array
    {
        $db = Database::connection();
        $stmt = $db->prepare("SELECT department_id FROM user_departments WHERE user_type=:t AND user_id=:id");
        $stmt->execute(['t' => $userType, 'id' => $userId]);
        return array_map('intval', array_column($stmt->fetchAll(), 'department_id'));
    }

    public static function currentProjectIds(string $userType, int $userId): array
    {
        $db = Database::connection();
        $stmt = $db->prepare("SELECT project_id FROM user_projects WHERE user_type=:t AND user_id=:id");
        $stmt->execute(['t' => $userType, 'id' => $userId]);
        return array_map('intval', array_column($stmt->fetchAll(), 'project_id'));
    }

    public static function currentRoleId(string $userType, int $userId): ?int
    {
        $db = Database::connection();
        $stmt = $db->prepare("SELECT role_id FROM user_roles WHERE user_type=:t AND user_id=:id LIMIT 1");
        $stmt->execute(['t' => $userType, 'id' => $userId]);
        $r = $stmt->fetch();
        return $r ? (int)$r['role_id'] : null;
    }

    /**
     * Set who a manager/employee reports to. The reporting manager must be
     * an existing manager or superadmin (per the reports_to_type enum).
     * Superadmins don't report to anyone in this system.
     */
    public static function setReportsTo(string $userType, int $userId, ?string $reportsToType, ?int $reportsToId): void
    {
        if (!in_array($userType, ['manager', 'employee'], true)) {
            return;
        }

        $table = $userType === 'manager' ? 'managers' : 'employees';
        $db = Database::connection();

        if (!$reportsToType || !$reportsToId) {
            $db->prepare("UPDATE `{$table}` SET reports_to_type = NULL, reports_to_id = NULL WHERE id = :id")
               ->execute(['id' => $userId]);
            return;
        }

        // Prevent a user from reporting to themselves
        if ($reportsToType === $userType && $reportsToId === $userId) return;

        $db->prepare("UPDATE `{$table}` SET reports_to_type = :t, reports_to_id = :r WHERE id = :id")
           ->execute(['t' => $reportsToType, 'r' => $reportsToId, 'id' => $userId]);
    }

    public static function currentReportsTo(string $userType, int $userId): ?array
    {
        if (!in_array($userType, ['manager', 'employee'], true)) return null;

        $table = $userType === 'manager' ? 'managers' : 'employees';
        $db = Database::connection();
        $stmt = $db->prepare("SELECT reports_to_type, reports_to_id FROM `{$table}` WHERE id = :id");
        $stmt->execute(['id' => $userId]);
        $row = $stmt->fetch();
        if (!$row || !$row['reports_to_type'] || !$row['reports_to_id']) return null;

        return [
            'type' => $row['reports_to_type'],
            'id'   => (int)$row['reports_to_id'],
            'name' => self::nameFor($row['reports_to_type'], (int)$row['reports_to_id']),
        ];
    }

    public static function reportsToLabel(string $userType, int $userId): string
    {
        $r = self::currentReportsTo($userType, $userId);
        return $r ? $r['name'] : '—';
    }

    /**
     * Every manager and employee anywhere below this manager in the reporting
     * chain (direct reports, their reports, and so on). Used for "full
     * hierarchy" visibility — e.g. Sara needs to see Cesar's activity AND
     * Karan/Harpreet's activity even though they report to Cesar, not to her
     * directly.
     *
     * @return array{manager: int[], employee: int[]}
     */
    public static function allSubordinateIds(int $managerId): array
    {
        $db = Database::connection();
        $out = ['manager' => [], 'employee' => []];

        $queue = [$managerId];
        $seen = [];
        while (!empty($queue)) {
            $currentId = array_shift($queue);
            if (isset($seen[$currentId])) continue;
            $seen[$currentId] = true;

            $stmt = $db->prepare(
                "SELECT id FROM managers WHERE reports_to_type = 'manager' AND reports_to_id = :id"
            );
            $stmt->execute(['id' => $currentId]);
            foreach ($stmt->fetchAll(PDO::FETCH_COLUMN) as $subManagerId) {
                $subManagerId = (int)$subManagerId;
                if (!in_array($subManagerId, $out['manager'], true)) {
                    $out['manager'][] = $subManagerId;
                    $queue[] = $subManagerId;
                }
            }

            $stmt = $db->prepare(
                "SELECT id FROM employees WHERE reports_to_type = 'manager' AND reports_to_id = :id"
            );
            $stmt->execute(['id' => $currentId]);
            foreach ($stmt->fetchAll(PDO::FETCH_COLUMN) as $subEmployeeId) {
                $subEmployeeId = (int)$subEmployeeId;
                if (!in_array($subEmployeeId, $out['employee'], true)) {
                    $out['employee'][] = $subEmployeeId;
                }
            }
        }

        return $out;
    }

    public static function nameFor(string $userType, int $userId): string
    {
        $db = Database::connection();
        $table = $userType === 'superadmin' ? 'superadmins' : ($userType === 'manager' ? 'managers' : 'employees');
        $nameCol = $userType === 'superadmin' ? 'full_name' : 'name';
        $stmt = $db->prepare("SELECT `{$nameCol}` AS n FROM `{$table}` WHERE id = :id");
        $stmt->execute(['id' => $userId]);
        $r = $stmt->fetch();
        return $r ? $r['n'] : '—';
    }
}
