<?php
declare(strict_types=1);

// Christo Lochenberg Developments | info@christodev.co.za | +27 61 523 9126

class User {
    private Database $db;

    public function __construct() {
        $this->db = new Database();
    }

    public function register(array $data): bool {
        $this->db->query('INSERT INTO users (name, company_name, email, password, email_verified, verification_token, contact_number, address_street, address_suburb, address_city, address_state, address_zip, address_country, role) VALUES(:name, :company_name, :email, :password, :email_verified, :verification_token, :contact_number, :address_street, :address_suburb, :address_city, :address_state, :address_zip, :address_country, :role)');
        
        $this->db->bind(':name', $data['name']);
        $this->db->bind(':company_name', $data['company_name']);
        $this->db->bind(':email', $data['email']);
        $this->db->bind(':password', $data['password']);
        $this->db->bind(':email_verified', 0); // Force email verification
        $this->db->bind(':verification_token', $data['verification_token']);
        $this->db->bind(':contact_number', $data['contact_number']);
        $this->db->bind(':address_street', $data['address_street']);
        $this->db->bind(':address_suburb', $data['address_suburb']);
        $this->db->bind(':address_city', $data['address_city']);
        $this->db->bind(':address_state', $data['address_state']);
        $this->db->bind(':address_zip', $data['address_zip']);
        $this->db->bind(':address_country', $data['address_country']);
        $this->db->bind(':role', $data['role']);

        return $this->db->execute();
    }

    public function findUserByEmailRecord(string $email): object|bool {
        $this->db->query('SELECT * FROM users WHERE email = :email');
        $this->db->bind(':email', $email);
        return $this->db->single();
    }

    public function login(string $email, string $password): object|bool {
        $user = $this->findUserByEmailRecord($email);

        if ($user && $user->is_active == 1 && $user->email_verified == 1) {
            if (password_verify($password, $user->password)) {
                return $user;
            }
        }
        return false;
    }

    public function findUserByEmail(string $email): bool {
        $this->db->query('SELECT id FROM users WHERE email = :email');
        $this->db->bind(':email', $email);
        $this->db->single();
        return $this->db->rowCount() > 0;
    }

    public function getUserById(int $id): object|bool {
        $this->db->query('SELECT users.*, staff_roles.role_name as staff_role_name, partners.name as partner_name FROM users LEFT JOIN staff_roles ON users.staff_role_id = staff_roles.id LEFT JOIN partners ON users.partner_id = partners.id WHERE users.id = :id');
        $this->db->bind(':id', $id);
        return $this->db->single();
    }

    public function recordFailedLogin(string $email): void {
        $this->db->query('UPDATE users SET failed_attempts = failed_attempts + 1, locked_until = CASE WHEN failed_attempts >= 4 THEN DATE_ADD(NOW(), INTERVAL 15 MINUTE) ELSE locked_until END WHERE email = :email');
        $this->db->bind(':email', $email);
        $this->db->execute();
    }

    public function resetFailedLogins(string $email): void {
        $this->db->query('UPDATE users SET failed_attempts = 0, locked_until = NULL WHERE email = :email');
        $this->db->bind(':email', $email);
        $this->db->execute();
    }

    public function verifyEmail(string $token): bool {
        $this->db->query('UPDATE users SET email_verified = 1, verification_token = NULL WHERE verification_token = :token');
        $this->db->bind(':token', $token);
        $this->db->execute();
        return $this->db->rowCount() > 0;
    }

    public function setRememberToken(int $userId, string $token): void {
        $this->db->query('UPDATE users SET remember_token = :token WHERE id = :id');
        $this->db->bind(':token', $token);
        $this->db->bind(':id', $userId);
        $this->db->execute();
    }

    public function findByRememberToken(string $token): object|bool {
        $this->db->query('SELECT * FROM users WHERE remember_token = :token');
        $this->db->bind(':token', $token);
        return $this->db->single();
    }

    public function updateProfile(array $data): bool {
        $this->db->query('UPDATE users SET name = :name, email = :email, contact_number = :contact_number, address_street = :address_street, address_suburb = :address_suburb, address_city = :address_city, address_state = :address_state, address_zip = :address_zip, address_country = :address_country WHERE id = :id');
        
        $this->db->bind(':id', $data['id']);
        $this->db->bind(':name', $data['name']);
        $this->db->bind(':email', $data['email']);
        $this->db->bind(':contact_number', $data['contact_number']);
        $this->db->bind(':address_street', $data['address_street']);
        $this->db->bind(':address_suburb', $data['address_suburb']);
        $this->db->bind(':address_city', $data['address_city']);
        $this->db->bind(':address_state', $data['address_state']);
        $this->db->bind(':address_zip', $data['address_zip']);
        $this->db->bind(':address_country', $data['address_country']);
        
        return $this->db->execute();
    }

    public function updatePassword(array $data): bool {
        $this->db->query('UPDATE users SET password = :password WHERE id = :id');
        $this->db->bind(':id', $data['id']);
        $this->db->bind(':password', $data['password']);
        return $this->db->execute();
    }

    public function addPasswordReset(string $email, string $token): bool {
        $this->db->query('DELETE FROM password_resets WHERE email = :email');
        $this->db->bind(':email', $email);
        $this->db->execute();

        $this->db->query('INSERT INTO password_resets (email, token) VALUES (:email, :token)');
        $this->db->bind(':email', $email);
        $this->db->bind(':token', $token);
        return $this->db->execute();
    }

    public function findPasswordReset(string $token): object|bool {
        $this->db->query('SELECT * FROM password_resets WHERE token = :token AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)');
        $this->db->bind(':token', $token);
        return $this->db->single();
    }

    public function updatePasswordByEmail(string $email, string $password): bool {
        $this->db->query('UPDATE users SET password = :password WHERE email = :email');
        $this->db->bind(':email', $email);
        $this->db->bind(':password', $password);
        return $this->db->execute();
    }

    public function deletePasswordReset(string $email): void {
        $this->db->query('DELETE FROM password_resets WHERE email = :email');
        $this->db->bind(':email', $email);
        $this->db->execute();
    }

    public function getUsers(): array {
        $this->db->query('SELECT users.*, staff_roles.role_name as staff_role_name, partners.name as partner_name FROM users LEFT JOIN staff_roles ON users.staff_role_id = staff_roles.id LEFT JOIN partners ON users.partner_id = partners.id ORDER BY users.created_at DESC');
        return $this->db->resultSet();
    }

    public function updateUserByAdmin(array $data): bool {
        $sql = 'UPDATE users SET name = :name, company_name = :company_name, email = :email, role = :role, is_active = :is_active, contact_number = :contact_number, address_street = :address_street, address_suburb = :address_suburb, address_city = :address_city, address_state = :address_state, address_zip = :address_zip, address_country = :address_country, staff_role_id = :staff_role_id, partner_id = :partner_id';
        
        if (!empty($data['password'])) {
            $sql .= ', password = :password';
        }
        
        $sql .= ' WHERE id = :id';

        $this->db->query($sql);
        $this->db->bind(':id', $data['id']);
        $this->db->bind(':name', $data['name']);
        $this->db->bind(':company_name', $data['company_name']);
        $this->db->bind(':email', $data['email']);
        $this->db->bind(':role', $data['role']);
        $this->db->bind(':is_active', $data['is_active']);
        $this->db->bind(':contact_number', $data['contact_number']);
        $this->db->bind(':address_street', $data['address_street']);
        $this->db->bind(':address_suburb', $data['address_suburb']);
        $this->db->bind(':address_city', $data['address_city']);
        $this->db->bind(':address_state', $data['address_state']);
        $this->db->bind(':address_zip', $data['address_zip']);
        $this->db->bind(':address_country', $data['address_country']);
        $this->db->bind(':staff_role_id', $data['staff_role_id']);
        $this->db->bind(':partner_id', $data['partner_id']);

        if (!empty($data['password'])) {
            $this->db->bind(':password', $data['password']);
        }

        return $this->db->execute();
    }

    public function addUserByAdmin(array $data): bool {
        $this->db->query('INSERT INTO users (name, company_name, email, password, role, is_active, email_verified, contact_number, address_street, address_suburb, address_city, address_state, address_zip, address_country, staff_role_id, partner_id) VALUES(:name, :company_name, :email, :password, :role, :is_active, 1, :contact_number, :address_street, :address_suburb, :address_city, :address_state, :address_zip, :address_country, :staff_role_id, :partner_id)');
        
        $this->db->bind(':name', $data['name']);
        $this->db->bind(':company_name', $data['company_name']);
        $this->db->bind(':email', $data['email']);
        $this->db->bind(':password', $data['password']);
        $this->db->bind(':role', $data['role']);
        $this->db->bind(':is_active', $data['is_active']);
        $this->db->bind(':contact_number', $data['contact_number']);
        $this->db->bind(':address_street', $data['address_street']);
        $this->db->bind(':address_suburb', $data['address_suburb']);
        $this->db->bind(':address_city', $data['address_city']);
        $this->db->bind(':address_state', $data['address_state']);
        $this->db->bind(':address_zip', $data['address_zip']);
        $this->db->bind(':address_country', $data['address_country']);
        $this->db->bind(':staff_role_id', $data['staff_role_id']);
        $this->db->bind(':partner_id', $data['partner_id']);

        return $this->db->execute();
    }

    public function deleteUser(int $id): bool {
        $this->db->query('DELETE FROM users WHERE id = :id');
        $this->db->bind(':id', $id);
        return $this->db->execute();
    }

    public function getUserStats(): array {
        $this->db->query('SELECT COUNT(*) as total, SUM(is_active = 1) as active FROM users');
        $result = $this->db->single();
        return ['total' => $result->total ?? 0, 'active' => $result->active ?? 0];
    }

    public function getRecentUsers(): array {
        $this->db->query('SELECT users.*, staff_roles.role_name as staff_role_name, partners.name as partner_name FROM users LEFT JOIN staff_roles ON users.staff_role_id = staff_roles.id LEFT JOIN partners ON users.partner_id = partners.id ORDER BY users.created_at DESC LIMIT 5');
        return $this->db->resultSet();
    }

    public function generateBackupCodes(int $userId, int $count = 8): array {
        $this->db->query("DELETE FROM user_backup_codes WHERE user_id = :user_id");
        $this->db->bind(':user_id', $userId);
        $this->db->execute();

        $plainCodes = [];
        for ($i = 0; $i < $count; $i++) {
            $code = strtoupper(substr(bin2hex(random_bytes(5)), 0, 8));
            $formattedCode = substr($code, 0, 4) . '-' . substr($code, 4, 4);
            $plainCodes[] = $formattedCode;

            $this->db->query("INSERT INTO user_backup_codes (user_id, code_hash) VALUES (:user_id, :code_hash)");
            $this->db->bind(':user_id', $userId);
            $this->db->bind(':code_hash', password_hash($formattedCode, PASSWORD_DEFAULT));
            $this->db->execute();
        }

        return $plainCodes;
    }

    public function verifyAndConsumeBackupCode(int $userId, string $plainCode): bool {
        $plainCode = strtoupper(str_replace([' ', '-'], '', $plainCode));
        if (strlen($plainCode) !== 8) return false;
        
        $plainCode = substr($plainCode, 0, 4) . '-' . substr($plainCode, 4, 4);

        $this->db->query("SELECT id, code_hash FROM user_backup_codes WHERE user_id = :user_id AND used = 0");
        $this->db->bind(':user_id', $userId);
        $codes = $this->db->resultSet();

        foreach ($codes as $codeRow) {
            if (password_verify($plainCode, $codeRow->code_hash)) {
                $this->db->query("UPDATE user_backup_codes SET used = 1, used_at = CURRENT_TIMESTAMP WHERE id = :id");
                $this->db->bind(':id', $codeRow->id);
                $this->db->execute();
                return true;
            }
        }

        return false;
    }

    public function getClients(): array {
        $this->db->query("SELECT users.*, partners.name as partner_name FROM users LEFT JOIN partners ON users.partner_id = partners.id WHERE users.role = 'client' ORDER BY users.created_at DESC");
        return $this->db->resultSet();
    }
}