<?php

namespace PloverCRM\Common;

/**
 * CRM-native user registry.
 *
 * PloverCRM users now live in a dedicated plugin table. Native CRM users are
 * backed by hidden low-privilege WordPress bridge users so existing internals
 * that still rely on a runtime WordPress user ID keep working while the product
 * stops treating `wp_users` as the source of truth.
 */
class CrmUserManager
{
    public const TABLE_NAME = 'plover_crm_users';
    public const SCHEMA_VERSION_OPTION = 'plover_crm_users_schema_version';
    public const SCHEMA_VERSION = '2';

    public const TYPE_NATIVE = 'native';
    public const TYPE_WORDPRESS_ADMIN = 'wordpress_admin';

    public const STATUS_ACTIVE = 'active';
    public const STATUS_DISABLED = 'disabled';

    public const AVATAR_SOURCE_NONE = 'none';
    public const AVATAR_SOURCE_CUSTOM = 'custom';
    public const AVATAR_SOURCE_WORDPRESS = 'wordpress';
    public const AVATAR_UPLOAD_FIELD = 'avatar';
    public const AVATAR_MAX_BYTES = 5 * MB_IN_BYTES;

    public const SHADOW_USER_META_KEY = 'plover_crm_shadow_user';
    public const SHADOW_USER_SOURCE_META_KEY = 'plover_crm_shadow_source';

    public static function init(): void
    {
        self::maybeCreateTable();
        add_action('deleted_user', [self::class, 'handleDeletedWordPressUser']);
        add_action('wpmu_delete_user', [self::class, 'handleDeletedWordPressUser']);
    }

    public static function getTableName(): string
    {
        global $wpdb;

        return $wpdb->prefix . self::TABLE_NAME;
    }

    public static function maybeCreateTable(): void
    {
        global $wpdb;

        $table = self::getTableName();
        $tableExists = self::tableExists($table);

        if (
            $tableExists &&
            get_option(self::SCHEMA_VERSION_OPTION) === self::SCHEMA_VERSION
        ) {
            return;
        }

        $charsetCollate = $wpdb->get_charset_collate();

        $sql = "CREATE TABLE $table (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            wp_user_id bigint(20) unsigned NOT NULL,
            username varchar(60) NOT NULL,
            email varchar(190) NOT NULL,
            display_name varchar(190) NOT NULL,
            role_id varchar(190) DEFAULT NULL,
            avatar_attachment_id bigint(20) unsigned DEFAULT NULL,
            user_type varchar(30) NOT NULL DEFAULT 'native',
            status varchar(20) NOT NULL DEFAULT 'active',
            created_at datetime NOT NULL,
            updated_at datetime NOT NULL,
            PRIMARY KEY (id),
            UNIQUE KEY wp_user_id (wp_user_id),
            UNIQUE KEY username (username),
            UNIQUE KEY email (email),
            KEY role_id (role_id),
            KEY avatar_attachment_id (avatar_attachment_id),
            KEY user_type (user_type),
            KEY status (status)
        ) $charsetCollate;";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta($sql);

        if (self::tableExists($table)) {
            update_option(self::SCHEMA_VERSION_OPTION, self::SCHEMA_VERSION, false);
        }
    }

    public static function createNativeUser(array $data)
    {
        $displayName = sanitize_text_field($data['display_name'] ?? '');
        $email = sanitize_email($data['email'] ?? '');
        $preferredUsername = sanitize_user($data['username'] ?? '', true);
        $roleId = sanitize_text_field($data['role_id'] ?? '');

        if ($displayName === '') {
            return new \WP_Error('missing_display_name', 'Display name is required.');
        }

        if ($email === '' || !is_email($email)) {
            return new \WP_Error('invalid_email', 'A valid email address is required.');
        }

        $wordpressAdminByEmail = get_user_by('email', $email);
        if ($wordpressAdminByEmail && user_can($wordpressAdminByEmail, 'manage_options')) {
            return new \WP_Error(
                'crm_email_reserved',
                'This email address belongs to a WordPress administrator and is reserved for owner access.'
            );
        }

        $role = RoleManager::getRole($roleId);
        if (!$role) {
            return new \WP_Error('invalid_crm_role', 'A valid CRM role is required.');
        }

        if (self::getByEmail($email)) {
            return new \WP_Error('crm_email_exists', 'A CRM user already exists with this email address.');
        }

        $username = self::generateUniqueCrmUsername($preferredUsername, $email, $displayName);

        if (!LicenseChecker::canAssignAnotherCrmUser()) {
            return self::seatLimitError();
        }

        $bridgeUserId = self::createBridgeWordPressUser($displayName);

        if (is_wp_error($bridgeUserId)) {
            return $bridgeUserId;
        }

        global $wpdb;

        $now = current_time('mysql', true);
        $inserted = $wpdb->insert(
            self::getTableName(),
            [
                'wp_user_id' => $bridgeUserId,
                'username' => $username,
                'email' => $email,
                'display_name' => $displayName,
                'role_id' => $roleId,
                'user_type' => self::TYPE_NATIVE,
                'status' => self::STATUS_ACTIVE,
                'created_at' => $now,
                'updated_at' => $now,
            ],
            ['%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']
        );

        if ($inserted === false) {
            require_once ABSPATH . 'wp-admin/includes/user.php';
            wp_delete_user($bridgeUserId);

            return new \WP_Error('crm_user_creation_failed', 'Unable to create this CRM user.');
        }

        $record = self::getByWordPressUserId($bridgeUserId);

        if (!$record) {
            require_once ABSPATH . 'wp-admin/includes/user.php';
            wp_delete_user($bridgeUserId);

            return new \WP_Error('crm_user_creation_failed', 'Unable to load the newly created CRM user.');
        }

        $passwordBundle = StandalonePasswordManager::regeneratePassword((int) $record['id'], [
            'rotated_by_user_id' => get_current_user_id() ?: null,
        ]);

        if (!$passwordBundle) {
            self::deleteNativeUser((int) $record['id']);

            return new \WP_Error(
                'password_generation_failed',
                'Unable to generate the standalone password for this user.'
            );
        }

        return [
            'user_id' => (int) $record['id'],
            'crm_user_id' => (int) $record['id'],
            'wp_user_id' => $bridgeUserId,
            'created' => true,
            'linked_existing_user' => false,
            'password_bundle' => $passwordBundle,
            'username' => $username,
            'email' => $email,
            'display_name' => $displayName,
            'wordpress_role' => 'subscriber',
            'role_id' => $roleId,
            'role_name' => $role['name'],
        ];
    }

    public static function ensureWordPressAdministratorRecord(int $wpUserId): ?array
    {
        $wpUser = get_userdata($wpUserId);

        if (!$wpUser || !user_can($wpUser, 'manage_options')) {
            return null;
        }

        $existing = self::getByWordPressUserId($wpUserId);

        if ($existing) {
            self::syncWordPressAdministratorRecord($existing, $wpUser);
            return self::getByWordPressUserId($wpUserId);
        }

        global $wpdb;

        $now = current_time('mysql', true);
        $wpdb->insert(
            self::getTableName(),
            [
                'wp_user_id' => $wpUserId,
                'username' => $wpUser->user_login,
                'email' => $wpUser->user_email,
                'display_name' => $wpUser->display_name ?: $wpUser->user_login,
                'role_id' => null,
                'user_type' => self::TYPE_WORDPRESS_ADMIN,
                'status' => self::STATUS_ACTIVE,
                'created_at' => $now,
                'updated_at' => $now,
            ],
            ['%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']
        );

        return self::getByWordPressUserId($wpUserId);
    }

    public static function getByPublicId(int $publicId): ?array
    {
        global $wpdb;

        $row = $wpdb->get_row(
            $wpdb->prepare(
                'SELECT * FROM ' . self::getTableName() . ' WHERE id = %d LIMIT 1',
                $publicId
            ),
            ARRAY_A
        );

        return is_array($row) ? self::normalizeRow($row) : null;
    }

    public static function getByWordPressUserId(int $wpUserId): ?array
    {
        global $wpdb;

        $row = $wpdb->get_row(
            $wpdb->prepare(
                'SELECT * FROM ' . self::getTableName() . ' WHERE wp_user_id = %d LIMIT 1',
                $wpUserId
            ),
            ARRAY_A
        );

        return is_array($row) ? self::normalizeRow($row) : null;
    }

    public static function getByUsername(string $username): ?array
    {
        global $wpdb;

        $username = sanitize_user($username, true);

        if ($username === '') {
            return null;
        }

        $row = $wpdb->get_row(
            $wpdb->prepare(
                'SELECT * FROM ' . self::getTableName() . ' WHERE username = %s LIMIT 1',
                $username
            ),
            ARRAY_A
        );

        return is_array($row) ? self::normalizeRow($row) : null;
    }

    public static function getByEmail(string $email): ?array
    {
        global $wpdb;

        $email = sanitize_email($email);

        if ($email === '') {
            return null;
        }

        $row = $wpdb->get_row(
            $wpdb->prepare(
                'SELECT * FROM ' . self::getTableName() . ' WHERE email = %s LIMIT 1',
                $email
            ),
            ARRAY_A
        );

        return is_array($row) ? self::normalizeRow($row) : null;
    }

    public static function getPublicProfileByWordPressUserId(int $wpUserId): ?array
    {
        $record = self::getByWordPressUserId($wpUserId);

        if ($record) {
            return $record;
        }

        $wpUser = get_userdata($wpUserId);

        if (!$wpUser || !user_can($wpUser, 'manage_options')) {
            return null;
        }

        return self::ensureWordPressAdministratorRecord($wpUserId);
    }

    public static function getPublicProfileById(int $publicId): ?array
    {
        return self::getByPublicId($publicId);
    }

    public static function getWordPressUserIdByPublicId(int $publicId): ?int
    {
        $record = self::getByPublicId($publicId);

        if (!$record || empty($record['wp_user_id'])) {
            return null;
        }

        return (int) $record['wp_user_id'];
    }

    public static function getPublicIdByWordPressUserId(int $wpUserId): ?int
    {
        $record = self::getPublicProfileByWordPressUserId($wpUserId);

        if (!$record || empty($record['id'])) {
            return null;
        }

        return (int) $record['id'];
    }

    public static function getCurrentProfile(): ?array
    {
        $wpUserId = get_current_user_id();

        if (!$wpUserId) {
            return null;
        }

        return self::getPublicProfileByWordPressUserId((int) $wpUserId);
    }

    public static function replaceAvatarForPublicUser(int $publicId, array $file)
    {
        $record = self::getByPublicId($publicId);

        if (!$record) {
            return new \WP_Error('user_not_found', 'Unable to resolve this CRM user.');
        }

        if (empty($file) || !is_array($file) || empty($file['tmp_name'])) {
            return new \WP_Error('avatar_missing', 'Choose an image to upload.');
        }

        if (!empty($file['error']) && (int) $file['error'] !== UPLOAD_ERR_OK) {
            return new \WP_Error('avatar_upload_failed', 'The selected image could not be uploaded.');
        }

        if (!empty($file['size']) && (int) $file['size'] > self::AVATAR_MAX_BYTES) {
            return new \WP_Error('avatar_too_large', 'Profile pictures must be 5 MB or smaller.');
        }

        require_once ABSPATH . 'wp-admin/includes/file.php';
        require_once ABSPATH . 'wp-admin/includes/image.php';
        require_once ABSPATH . 'wp-admin/includes/media.php';

        $attachmentId = media_handle_upload(
            self::AVATAR_UPLOAD_FIELD,
            0,
            [
                'post_title' => sprintf('%s profile picture', $record['display_name']),
            ],
            [
                'test_form' => false,
                'mimes' => self::getAllowedAvatarMimeTypes(),
            ]
        );

        if (is_wp_error($attachmentId)) {
            return new \WP_Error(
                'avatar_upload_failed',
                $attachmentId->get_error_message() ?: 'The selected image could not be uploaded.'
            );
        }

        global $wpdb;

        $updated = $wpdb->update(
            self::getTableName(),
            [
                'avatar_attachment_id' => (int) $attachmentId,
                'updated_at' => current_time('mysql', true),
            ],
            ['id' => (int) $record['id']],
            ['%d', '%s'],
            ['%d']
        );

        if ($updated === false) {
            wp_delete_attachment((int) $attachmentId, true);

            return new \WP_Error(
                'avatar_update_failed',
                'The profile picture was uploaded, but the CRM user could not be updated.'
            );
        }

        self::deleteAvatarAttachmentId((int) ($record['avatar_attachment_id'] ?? 0), (int) $attachmentId);

        return self::getByPublicId($publicId);
    }

    public static function removeAvatarForPublicUser(int $publicId): bool
    {
        $record = self::getByPublicId($publicId);

        if (!$record) {
            return false;
        }

        $currentAttachmentId = (int) ($record['avatar_attachment_id'] ?? 0);

        if ($currentAttachmentId <= 0) {
            return true;
        }

        global $wpdb;

        $updated = $wpdb->update(
            self::getTableName(),
            [
                'avatar_attachment_id' => null,
                'updated_at' => current_time('mysql', true),
            ],
            ['id' => (int) $record['id']],
            ['%d', '%s'],
            ['%d']
        );

        if ($updated === false) {
            return false;
        }

        self::deleteAvatarAttachmentId($currentAttachmentId);

        return true;
    }

    public static function updateRoleForPublicUser(int $publicId, ?string $roleId): bool
    {
        $record = self::getByPublicId($publicId);

        if (!$record) {
            $record = self::ensureWordPressAdministratorRecord($publicId);
        }

        if (!$record) {
            return false;
        }

        global $wpdb;

        $updated = $wpdb->update(
            self::getTableName(),
            [
                'role_id' => $roleId !== '' ? $roleId : null,
                'updated_at' => current_time('mysql', true),
            ],
            [
                'id' => (int) $record['id'],
            ],
            ['%s', '%s'],
            ['%d']
        );

        return $updated !== false;
    }

    public static function userHasAssignedRole(int $publicId): bool
    {
        $record = self::getByPublicId($publicId);

        return !empty($record['role_id']);
    }

    public static function countAssignedUsers(): int
    {
        global $wpdb;

        return (int) $wpdb->get_var(
            $wpdb->prepare(
                'SELECT COUNT(*) FROM ' . self::getTableName() . ' WHERE user_type = %s AND role_id IS NOT NULL AND role_id != %s',
                self::TYPE_NATIVE,
                ''
            )
        );
    }

    public static function getNativeUsersWithRoles(): array
    {
        global $wpdb;

        $rows = $wpdb->get_results(
            $wpdb->prepare(
                'SELECT * FROM ' . self::getTableName() . ' WHERE user_type = %s ORDER BY display_name ASC, username ASC',
                self::TYPE_NATIVE
            ),
            ARRAY_A
        );

        return array_map([self::class, 'normalizeRow'], is_array($rows) ? $rows : []);
    }

    public static function getWordPressAdministratorsOverview(): array
    {
        $wpAdmins = get_users([
            'role__in' => ['administrator'],
            'orderby' => 'display_name',
            'order' => 'ASC',
        ]);

        $result = [];

        foreach ($wpAdmins as $wpAdmin) {
            if (self::isShadowWordPressUser((int) $wpAdmin->ID)) {
                continue;
            }

            $record = self::ensureWordPressAdministratorRecord((int) $wpAdmin->ID);

            if (!$record) {
                continue;
            }

            $result[] = $record;
        }

        return $result;
    }

    public static function deleteNativeUser(int $publicId): bool
    {
        $record = self::getByPublicId($publicId);

        if (!$record || $record['user_type'] !== self::TYPE_NATIVE) {
            return false;
        }

        StandalonePasswordManager::revokePasswordForUser((int) $record['id']);
        self::deleteAvatarAttachmentId((int) ($record['avatar_attachment_id'] ?? 0));

        global $wpdb;
        $deleted = $wpdb->delete(
            self::getTableName(),
            ['id' => (int) $record['id']],
            ['%d']
        );

        if ($deleted === false) {
            return false;
        }

        if (self::isShadowWordPressUser((int) $record['wp_user_id'])) {
            require_once ABSPATH . 'wp-admin/includes/user.php';
            wp_delete_user((int) $record['wp_user_id']);
        }

        return true;
    }

    public static function handleDeletedWordPressUser(int $userId): void
    {
        $record = self::getByWordPressUserId($userId);

        if ($record) {
            self::deleteAvatarAttachmentId((int) ($record['avatar_attachment_id'] ?? 0));
        }

        global $wpdb;

        $wpdb->delete(
            self::getTableName(),
            ['wp_user_id' => $userId],
            ['%d']
        );
    }

    public static function isShadowWordPressUser(int $wpUserId): bool
    {
        return (bool) get_user_meta($wpUserId, self::SHADOW_USER_META_KEY, true);
    }

    public static function blockShadowUserLogin($user)
    {
        if (is_wp_error($user) || !$user instanceof \WP_User) {
            return $user;
        }

        if (!self::isShadowWordPressUser((int) $user->ID)) {
            return $user;
        }

        return new \WP_Error(
            'plover_crm_shadow_user_blocked',
            'This account cannot sign in to WordPress directly.'
        );
    }

    public static function shouldHideShadowUsersInQuery(\WP_User_Query $query): void
    {
        if (!is_admin()) {
            return;
        }

        global $pagenow;

        if ($pagenow !== 'users.php') {
            return;
        }

        if (!current_user_can('list_users')) {
            return;
        }

        $metaQuery = (array) $query->get('meta_query');
        $metaQuery[] = [
            'relation' => 'OR',
            [
                'key' => self::SHADOW_USER_META_KEY,
                'compare' => 'NOT EXISTS',
            ],
            [
                'key' => self::SHADOW_USER_META_KEY,
                'value' => '1',
                'compare' => '!=',
            ],
        ];

        $query->set('meta_query', $metaQuery);
    }

    private static function createBridgeWordPressUser(string $displayName)
    {
        $suffix = wp_generate_password(12, false, false);
        $login = sanitize_user('plvcrm_bridge_' . strtolower($suffix), true);
        $email = 'bridge-' . strtolower($suffix) . '@plovercrm.local';

        $userId = wp_insert_user([
            'user_login' => $login,
            'user_pass' => wp_generate_password(32, true, true),
            'user_email' => $email,
            'display_name' => $displayName,
            'role' => 'subscriber',
        ]);

        if (is_wp_error($userId)) {
            return $userId;
        }

        update_user_meta($userId, self::SHADOW_USER_META_KEY, 1);
        update_user_meta($userId, self::SHADOW_USER_SOURCE_META_KEY, 'crm_native_user');
        update_user_meta($userId, 'show_admin_bar_front', 'false');

        return (int) $userId;
    }

    private static function syncWordPressAdministratorRecord(array $record, \WP_User $wpUser): void
    {
        $displayName = $wpUser->display_name ?: $wpUser->user_login;

        if (
            $record['username'] === $wpUser->user_login &&
            $record['email'] === $wpUser->user_email &&
            $record['display_name'] === $displayName &&
            $record['user_type'] === self::TYPE_WORDPRESS_ADMIN
        ) {
            return;
        }

        global $wpdb;

        $wpdb->update(
            self::getTableName(),
            [
                'username' => $wpUser->user_login,
                'email' => $wpUser->user_email,
                'display_name' => $displayName,
                'user_type' => self::TYPE_WORDPRESS_ADMIN,
                'status' => self::STATUS_ACTIVE,
                'updated_at' => current_time('mysql', true),
            ],
            ['wp_user_id' => $record['wp_user_id']],
            ['%s', '%s', '%s', '%s', '%s', '%s'],
            ['%d']
        );
    }

    private static function generateUniqueCrmUsername(string $preferredUsername, string $email, string $displayName): string
    {
        $base = $preferredUsername;

        if ($base === '') {
            $emailLocalPart = sanitize_user(strtok($email, '@') ?: '', true);
            $namePart = sanitize_user($displayName, true);
            $base = $emailLocalPart !== '' ? $emailLocalPart : $namePart;
        }

        if ($base === '') {
            $base = 'crm_user';
        }

        $candidate = $base;
        $suffix = 2;

        while (self::getByUsername($candidate)) {
            $candidate = $base . $suffix;
            $suffix++;
        }

        while (self::isReservedWordPressAdministratorUsername($candidate)) {
            $candidate = $base . $suffix;
            $suffix++;
        }

        return $candidate;
    }

    private static function normalizeRow(array $row): array
    {
        $crmUserId = (int) ($row['id'] ?? 0);
        $avatarAttachmentId = (int) ($row['avatar_attachment_id'] ?? 0);
        $avatar = self::resolveAvatar(
            $avatarAttachmentId,
            (int) ($row['wp_user_id'] ?? 0),
            (string) ($row['user_type'] ?? self::TYPE_NATIVE)
        );

        return [
            'id' => $crmUserId,
            'crm_user_id' => $crmUserId,
            'internal_id' => $crmUserId,
            'wp_user_id' => (int) ($row['wp_user_id'] ?? 0),
            'username' => (string) ($row['username'] ?? ''),
            'email' => (string) ($row['email'] ?? ''),
            'display_name' => (string) ($row['display_name'] ?? ''),
            'role_id' => !empty($row['role_id']) ? (string) $row['role_id'] : null,
            'avatar_attachment_id' => $avatarAttachmentId > 0 ? $avatarAttachmentId : null,
            'avatar_url' => $avatar['url'],
            'avatar_source' => $avatar['source'],
            'user_type' => (string) ($row['user_type'] ?? self::TYPE_NATIVE),
            'status' => (string) ($row['status'] ?? self::STATUS_ACTIVE),
            'created_at' => (string) ($row['created_at'] ?? ''),
            'updated_at' => (string) ($row['updated_at'] ?? ''),
        ];
    }

    private static function seatLimitError(): \WP_Error
    {
        $usage = LicenseChecker::getCrmUserSeatUsage();

        return new \WP_Error(
            'crm_user_limit_reached',
            'CRM user limit reached for this site. Remove a CRM user or upgrade your license.',
            [
                'seat_limit' => $usage['limit'],
                'seat_used' => $usage['used'],
            ]
        );
    }

    private static function tableExists(string $table): bool
    {
        global $wpdb;

        return $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) === $table;
    }

    private static function isReservedWordPressAdministratorUsername(string $username): bool
    {
        $wpUser = get_user_by('login', $username);

        return (bool) ($wpUser && user_can($wpUser, 'manage_options'));
    }

    private static function resolveAvatar(int $attachmentId, int $wpUserId, string $userType): array
    {
        if ($attachmentId > 0) {
            $customAvatarUrl = wp_get_attachment_image_url($attachmentId, 'thumbnail');

            if (is_string($customAvatarUrl) && $customAvatarUrl !== '') {
                return [
                    'url' => $customAvatarUrl,
                    'source' => self::AVATAR_SOURCE_CUSTOM,
                ];
            }
        }

        if ($userType === self::TYPE_WORDPRESS_ADMIN && $wpUserId > 0) {
            $wordPressAvatarUrl = get_avatar_url($wpUserId, ['size' => 128]);

            if (is_string($wordPressAvatarUrl) && $wordPressAvatarUrl !== '') {
                return [
                    'url' => $wordPressAvatarUrl,
                    'source' => self::AVATAR_SOURCE_WORDPRESS,
                ];
            }
        }

        return [
            'url' => null,
            'source' => self::AVATAR_SOURCE_NONE,
        ];
    }

    private static function deleteAvatarAttachmentId(int $attachmentId, int $skipId = 0): void
    {
        if ($attachmentId <= 0 || $attachmentId === $skipId) {
            return;
        }

        wp_delete_attachment($attachmentId, true);
    }

    private static function getAllowedAvatarMimeTypes(): array
    {
        return [
            'jpg|jpeg|jpe' => 'image/jpeg',
            'png' => 'image/png',
            'gif' => 'image/gif',
            'webp' => 'image/webp',
            'avif' => 'image/avif',
        ];
    }
}
