<?php

class LoginCredentials
{
    private string $username;
    private string $password;

    public function __construct(string $username, string $password)
    {
        // More intensive SQL injection checks (example - adapt as needed)
        if (preg_match('/[\'";\-\_]/', $username) || preg_match('/[\'";\-\_]/', $password)) {
            throw new InvalidArgumentException('Invalid characters in username or password.');
        }
        // Consider using a more robust validation library

        $this->username = $username;
        $this->password = $password;
    }

    public function getUsername(): string
    {
        return $this->username;
    }

    public function getPassword(): string
    {
        return $this->password;
    }
}

class User
{
    private int $id;
    private ?array $spaces;
    private string $username;
    private ?DateTime $createdAt;
    private ?DateTime $lastActive;
    private bool $isActive;
    private ?string $profilePicture;
    private ?string $bio;
    private ?string $website;
    private int $role;

    public function __construct(int $id = -1, ?array $spaces, string $username, ?string $profilePicture = null, ?string $bio = null, ?string $website = null, int $role = 3)
    {
        // Basic XSS prevention on construction (can be enhanced)
        $username = htmlspecialchars($username, ENT_QUOTES, 'UTF-8');
        $bio = htmlspecialchars($bio ?? '', ENT_QUOTES, 'UTF-8');
        $website = htmlspecialchars($website ?? '', ENT_QUOTES, 'UTF-8');
        $profilePicture = htmlspecialchars($profilePicture ?? 'default-profile.png', ENT_QUOTES, 'UTF-8');

        // Basic SQL injection prevention (should primarily rely on prepared statements)
        if (preg_match('/[\'";\-\_]/', $username) || preg_match('/[\'";\-\_]/', $bio) || preg_match('/[\'";\-\_]/', $website) || preg_match('/[\'";\-\_]/', $profilePicture)) {
            throw new InvalidArgumentException('Invalid characters in user data.');
        }

        $this->username = $username;
        $this->profilePicture = $profilePicture;
        $this->bio = $bio;
        $this->website = $website;
        $this->createdAt = new DateTime();
        $this->lastActive = null;
        $this->isActive = false;
        $this->role = $role;
        $this->spaces = null;

        if ($id == -1) {
            $this->id = randomId(0);
            $stmt = 'INSERT INTO user (userId, username, profilePicture, bio, website) VALUES (?, ?, ?, ?, ?)';
            exec_stmt($stmt, 'issss', $this->id, $this->username, $this->profilePicture, $this->bio, $this->website);
        } else
            $this->id = $id;
    }

    public static function exists(int $id): bool
    {
        $stmt = 'SELECT count(id) FROM user WHERE id = ?';
        $result = exec_stmt($stmt, 'i', $id)->fetch_assoc();

        if ($result['count(id)'] == 1)
            return true;
        else if ($result['count(id)'] > 1)
            return true;
        else
            return false;
    }

    public static function retrieveFromDB(int $userId): ?User
    {
        $stmt = 'SELECT id, username, profilePicture, bio, website FROM user WHERE id = ?';
        $user = exec_stmt($stmt, 'i', $userId)->fetch_assoc();

        if ($user) {
            return new User($user['id'], null, $user['username'], $user['profilePicture'], $user['bio'], $user['website']);
        } else {
            return null;
        }
    }

    public static function getSignupHTML()
    {
        $html = '
            <div class="form_card">
                <h3 class="underline">Sign Up</h3>
                <form method="post" enctype="multipart/form-data">
                    <input type="hidden" name="form_id" value="signup_form">
                    <input type="hidden" name="crop_x" value="" id="crop_x">
                    <input type="hidden" name="crop_y" value="" id="crop_y">
                    <input type="hidden" name="crop_width" value="" id="crop_width">
                    <input name="required_field" id="required_field" value="">

                    <label for="username">Username</label>
                    <input id="username" name="username" required>

                    <label for="email">Email Address</label>
                    <input id="email" name="email" required>

                    <label for="password">Password</label>
                    <input id="password" name="password" type="password" required>

                    <label for="verify_password">Verify Password</label>
                    <input id="verify_password" name="verify_password" type="password" required>

                    <label for="profile_picture_input" class="upload_button modal_button" id="profile_cropper_button">Upload a Profile Picture</label>
                    <input id="profile_picture_input" class="file_input" name="profile_picture" type="file" accept="image/png, image/jpeg, image/jpg">
                    <small class="file_input_feedback">No file selected.</small>

                    <label class="form_checkbox_container">Stay Logged In?
                        <input name="stay_logged_in" type="checkbox" value="true">
                        <span class="checkmark"></span>
                    </label>

                    <input class="button" type="submit" value="Sign Up">
                </form>

                <div id="modal" class="modal">
                    <div id="display_modal"></div>
                </div>

                <div style="display: none;">
                    <div id="profile_cropper" class="modal_form">' . profile_cropper() . '</div>
                </div>
            </div>
        ';

        return $html;
    }

    public static function getLoginHTML()
    {
        $html = '
            <div class="form_card">
                <h3 class="underline">Log In</h3>
                <form method="post" id="login_form" action="director.php">
                    <input type="hidden" name="form_id" value="login_form">
                    <input type="hidden" name="password_hash" value="" id="password_hash">

                    <label for="username">Username / Email</label>
                    <input id="username" name="username" spellcheck="false" required autofocus>

                    <label for="password">Password</label>
                    <input id="password" name="password" type="password" spellcheck="false" required>

                    <label class="form_checkbox_container">Stay Logged In?
                        <input name="stay_logged_in" type="checkbox" value="true">
                        <span class="checkmark"></span>
                    </label>

                    <input class="button" type="submit" value="Log In">
                </form>

                <script src="js/login.js" defer></script>
            </div>
        ';

        return $html;
    }

    public static function login(LoginCredentials $credentials): ?User
    {
        $stmt = 'SELECT id, username, email, passwordHash, role, profilePicture, bio, website FROM user WHERE username = ?';
        $user = exec_stmt($stmt, 's', $credentials->getUsername())->fetch_assoc();

        if (!password_verify($credentials->getPassword(), $user['passwordHash']))
            return null;
        else
            return new User($user['id'], null, $user['username'], $user['profilePicture'], $user['bio'], $user['website']);
    }

    public static function logout(): void
    {
        foreach (array_keys($_SESSION) as $key) {
            unset($_SESSION[$key]);
        }
        // Consider destroying the session cookie as well: session_destroy();
        header('Location: .');  // Redirect to the homepage or login page
        exit();
    }

    public function getHomeSpace()
    {
        // TODO: Need to implement. Requires fleshing out additional space types.
        return Space::retrieveFromDB(1, 1);
    }

    public function getSpaces()
    {
        $stmt = 'SELECT * FROM spaceMembers WHERE user = ?';
        $spaces = [];
        $rawSpaces = exec_stmt($stmt, 'i', $this->id);
        while ($row = $rawSpaces->fetch_assoc()) {
            $spaces[] = Space::retrieveFromDB($row['space'], $row['user']);
        }

        $this->spaces = $spaces;
        return $spaces;
    }

    public function getRole(): int
    {
        return $this->role;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getUsername(): string
    {
        return $this->username;
    }

    public function getCreatedAt(): ?DateTime
    {
        return $this->createdAt;
    }

    public function getLastActive(): ?DateTime
    {
        return $this->lastActive;
    }

    public function isActive(): bool
    {
        return $this->isActive;
    }

    public function getProfilePicture(): ?string
    {
        return $this->profilePicture;
    }

    public function getBio(): ?string
    {
        return $this->bio;
    }

    public function getWebsite(): ?string
    {
        return $this->website;
    }

    public function setLastActive(?DateTime $lastActive): void
    {
        $this->lastActive = $lastActive;
    }

    public function setIsActive(bool $isActive): void
    {
        $this->isActive = $isActive;
    }

    public function toggleActive(?mysqli $db): bool
    {
        if (!$db) {
            error_log('Database connection not provided for toggleActive.');
            return false;
        }
        $this->isActive = !$this->isActive;
        $stmt = $db->prepare('UPDATE user SET is_active = ? WHERE user_id = ?');
        if ($stmt) {
            $stmt->bind_param('ii', (int) $this->isActive, $this->id);
            $result = $stmt->execute();
            $stmt->close();
            return $result;
        } else {
            error_log('Error preparing statement: ' . $db->error);
            return false;
        }
    }

    public function updateUsername(string $newUsername, ?mysqli $db): bool
    {
        $newUsername = htmlspecialchars($newUsername, ENT_QUOTES, 'UTF-8');
        if (preg_match('/[\'";\-\_]/', $newUsername)) {
            throw new InvalidArgumentException('Invalid characters in username.');
        }
        if (!$db) {
            error_log('Database connection not provided for updateUsername.');
            return false;
        }
        $stmt = $db->prepare('UPDATE user SET username = ? WHERE user_id = ?');
        if ($stmt) {
            $stmt->bind_param('si', $newUsername, $this->id);
            $result = $stmt->execute();
            $stmt->close();
            if ($result) {
                $this->username = $newUsername;
                return true;
            }
        } else {
            error_log('Error preparing statement: ' . $db->error);
        }
        return false;
    }

    public function updatePassword(string $newPassword, ?mysqli $db): bool
    {
        $newPasswordHash = hash('sha256', $newPassword);
        if (!$db) {
            error_log('Database connection not provided for updatePassword.');
            return false;
        }
        $stmt = $db->prepare('UPDATE user SET password_hash = ? WHERE user_id = ?');
        if ($stmt) {
            $stmt->bind_param('si', $newPasswordHash, $this->id);
            $result = $stmt->execute();
            $stmt->close();
            return $result;
        } else {
            error_log('Error preparing statement: ' . $db->error);
        }
        return false;
    }

    public function updateProfilePicture(?string $newProfilePicture, ?mysqli $db): bool
    {
        $newProfilePicture = htmlspecialchars($newProfilePicture ?? 'default-profile.png', ENT_QUOTES, 'UTF-8');
        if (preg_match('/[\'";\-\_]/', $newProfilePicture)) {
            throw new InvalidArgumentException('Invalid characters in profile picture filename.');
        }
        if (!$db) {
            error_log('Database connection not provided for updateProfilePicture.');
            return false;
        }
        $stmt = $db->prepare('UPDATE user SET profile_picture = ? WHERE user_id = ?');
        if ($stmt) {
            $stmt->bind_param('si', $newProfilePicture, $this->id);
            $result = $stmt->execute();
            $stmt->close();
            if ($result) {
                $this->profilePicture = $newProfilePicture;
                return true;
            }
        } else {
            error_log('Error preparing statement: ' . $db->error);
        }
        return false;
    }

    public function followUser(int $followingUserId, bool $notify = false, ?mysqli $db): bool
    {
        if (!$db) {
            error_log('Database connection not provided for followUser.');
            return false;
        }
        $stmt = $db->prepare('INSERT INTO follows (follower_user_id, following_user_id, notify_user) VALUES (?, ?, ?)');
        if ($stmt) {
            $stmt->bind_param('iii', $this->id, $followingUserId, (int) $notify);
            $result = $stmt->execute();
            $stmt->close();
            return $result;
        } else {
            error_log('Error preparing statement: ' . $db->error);
            return false;
        }
    }

    public function getFollowers(?mysqli $db): array
    {
        if (!$db) {
            error_log('Database connection not provided for getFollowers.');
            return [];
        }
        $stmt = $db->prepare('SELECT u.user_id, u.username, u.profile_picture, u.bio, u.website FROM follows f JOIN user u ON f.follower_user_id = u.user_id WHERE f.following_user_id = ?');
        if ($stmt) {
            $stmt->bind_param('i', $this->id);
            $stmt->execute();
            $result = $stmt->get_result();
            $followers = [];
            while ($row = $result->fetch_assoc()) {
                $followers[] = new User(
                    (int) $row['user_id'],
                    $row['username'],
                    $row['profile_picture'],
                    $row['bio'],
                    $row['website']
                );
            }
            $stmt->close();
            return $followers;
        } else {
            error_log('Error preparing statement: ' . $db->error);
            return [];
        }
    }

    public function getFollowing(?mysqli $db): array
    {
        if (!$db) {
            error_log('Database connection not provided for getFollowing.');
            return [];
        }
        $stmt = $db->prepare('SELECT u.user_id, u.username, u.profile_picture, u.bio, u.website FROM follows f JOIN user u ON f.following_user_id = u.user_id WHERE f.follower_user_id = ?');
        if ($stmt) {
            $stmt->bind_param('i', $this->id);
            $stmt->execute();
            $result = $stmt->get_result();
            $following = [];
            while ($row = $result->fetch_assoc()) {
                $following[] = new User(
                    (int) $row['user_id'],
                    $row['username'],
                    $row['profile_picture'],
                    $row['bio'],
                    $row['website']
                );
            }
            $stmt->close();
            return $following;
        } else {
            error_log('Error preparing statement: ' . $db->error);
            return [];
        }
    }
}

class Creator extends User {}
