/**
 * Passkeys.js - Frontend WebAuthn/Passkey Management
 *
 * This module handles passkey (WebAuthn) registration, authentication, and management.
 * Passkeys provide phishing-resistant, passwordless authentication using biometrics
 * or hardware security keys (Touch ID, Face ID, Windows Hello, YubiKey, etc.)
 */

const Passkeys = {
    /**
     * Check if WebAuthn is supported in the browser
     * @returns {boolean}
     */
    isSupported() {
        return window.PublicKeyCredential !== undefined &&
               navigator.credentials !== undefined;
    },

    /**
     * Show browser not supported error
     */
    showNotSupportedError() {
        showNotification('error', 'Browser Not Supported',
            'Your browser does not support passkeys. Please use a modern browser like Chrome, Safari, Edge, or Firefox.');
    },

    /**
     * Register a new passkey
     */
    async registerPasskey() {
        if (!this.isSupported()) {
            this.showNotSupportedError();
            return;
        }

        try {
            // Prompt for device name
            const deviceName = await this.promptDeviceName();
            if (!deviceName) {return;} // User cancelled

            const token = localStorage.getItem('authToken') || sessionStorage.getItem('authToken');
            if (!token) {
                showNotification('error', 'Authentication Required', 'Please log in first');
                return;
            }

            // Get registration options from server
            showNotification('info', 'Starting Registration', 'Requesting registration options...');

            const optionsResponse = await fetch('/api/auth/passkey/register/options', {
                method: 'POST',
                headers: {
                    'Authorization': `Bearer ${token}`,
                    'Content-Type': 'application/json'
                }
            });

            if (!optionsResponse.ok) {
                const error = await optionsResponse.json();
                throw new Error(error.error || 'Failed to get registration options');
            }

            const { options } = await optionsResponse.json();

            // Convert options for WebAuthn API
            const publicKeyOptions = this.convertRegistrationOptions(options);

            // Prompt user for biometric/security key
            showNotification('info', 'Authenticate', 'Please authenticate with your device (Touch ID, Face ID, Windows Hello, or security key)...');

            let credential;
            try {
                credential = await navigator.credentials.create({
                    publicKey: publicKeyOptions
                });
            } catch (err) {
                if (err.name === 'NotAllowedError') {
                    showNotification('warning', 'Cancelled', 'Registration was cancelled');
                } else if (err.name === 'InvalidStateError') {
                    showNotification('error', 'Already Registered', 'This authenticator is already registered');
                } else {
                    throw err;
                }
                return;
            }

            if (!credential) {
                showNotification('error', 'Registration Failed', 'No credential was created');
                return;
            }

            // Send credential to server for verification
            const verificationResponse = await fetch('/api/auth/passkey/register/verify', {
                method: 'POST',
                headers: {
                    'Authorization': `Bearer ${token}`,
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    response: this.encodeCredential(credential),
                    deviceName: deviceName
                })
            });

            if (!verificationResponse.ok) {
                const error = await verificationResponse.json();
                throw new Error(error.error || 'Failed to verify registration');
            }

            const result = await verificationResponse.json();
            showNotification('success', 'Passkey Registered', `${deviceName} has been successfully registered!`);

            // Reload passkey list
            if (typeof loadPasskeyStatus === 'function') {
                loadPasskeyStatus();
            }
            if (typeof listPasskeys === 'function') {
                listPasskeys();
            }

        } catch (error) {
            console.error('Passkey registration error:', error);
            showNotification('error', 'Registration Error', error.message);
        }
    },

    /**
     * Prompt user for device name
     * @returns {Promise<string|null>}
     */
    async promptDeviceName() {
        return new Promise((resolve) => {
            const modal = document.createElement('div');
            modal.className = 'modal-overlay';
            modal.innerHTML = `
                <div class="modal-content" style="max-width: 400px;">
                    <div class="modal-header">
                        <h2><i class="fas fa-fingerprint"></i> Register Passkey</h2>
                        <button class="modal-close" onclick="this.closest('.modal-overlay').remove(); event.stopPropagation();">
                            <i class="fas fa-times"></i>
                        </button>
                    </div>
                    <div class="modal-body">
                        <p style="margin-bottom: 15px;">Give this passkey a name to help you identify it later:</p>
                        <div class="form-group">
                            <label for="passkey-device-name">Device Name</label>
                            <input type="text" id="passkey-device-name"
                                   placeholder="e.g., iPhone 12, MacBook Pro, YubiKey"
                                   maxlength="100" required autofocus>
                            <small style="color: var(--gray-500); margin-top: 5px; display: block;">
                                Examples: "iPhone 15", "Windows PC", "YubiKey 5C"
                            </small>
                        </div>
                    </div>
                    <div class="modal-footer">
                        <button class="btn btn-secondary" onclick="this.closest('.modal-overlay').remove();">
                            Cancel
                        </button>
                        <button class="btn btn-primary" id="continue-registration-btn">
                            <i class="fas fa-arrow-right"></i> Continue
                        </button>
                    </div>
                </div>
            `;

            document.body.appendChild(modal);

            const input = modal.querySelector('#passkey-device-name');
            const continueBtn = modal.querySelector('#continue-registration-btn');

            // Auto-suggest device name
            input.value = this.suggestDeviceName();
            input.select();

            const handleContinue = () => {
                const value = input.value.trim();
                if (value) {
                    modal.remove();
                    resolve(value);
                } else {
                    input.style.borderColor = 'var(--danger)';
                    showNotification('warning', 'Name Required', 'Please enter a device name');
                }
            };

            continueBtn.addEventListener('click', handleContinue);
            input.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') {
                    e.preventDefault();
                    handleContinue();
                }
            });

            // Handle modal close
            modal.addEventListener('click', (e) => {
                if (e.target === modal) {
                    modal.remove();
                    resolve(null);
                }
            });
        });
    },

    /**
     * Suggest a device name based on user agent
     * @returns {string}
     */
    suggestDeviceName() {
        const ua = navigator.userAgent;

        // Mobile devices
        if (/iPhone/.test(ua)) {return 'iPhone';}
        if (/iPad/.test(ua)) {return 'iPad';}
        if (/Android/.test(ua)) {
            if (/Mobile/.test(ua)) {return 'Android Phone';}
            return 'Android Tablet';
        }

        // Desktop OS
        if (/Macintosh/.test(ua)) {return 'Mac';}
        if (/Windows/.test(ua)) {return 'Windows PC';}
        if (/Linux/.test(ua)) {return 'Linux PC';}
        if (/CrOS/.test(ua)) {return 'Chromebook';}

        return 'My Device';
    },

    /**
     * Convert registration options from server to WebAuthn format
     * @param {object} options - Options from server
     * @returns {object} - WebAuthn PublicKeyCredentialCreationOptions
     */
    convertRegistrationOptions(options) {
        return {
            challenge: this.base64urlToBuffer(options.challenge),
            rp: options.rp,
            user: {
                id: this.stringToBuffer(options.user.id), // Handle both base64url and plain text
                name: options.user.name,
                displayName: options.user.displayName
            },
            pubKeyCredParams: options.pubKeyCredParams,
            timeout: options.timeout,
            attestation: options.attestation,
            excludeCredentials: options.excludeCredentials?.map(cred => ({
                id: this.base64urlToBuffer(cred.id),
                type: cred.type,
                transports: cred.transports
            })) || [],
            authenticatorSelection: options.authenticatorSelection
        };
    },

    /**
     * Convert authentication options from server to WebAuthn format
     * @param {object} options - Options from server
     * @returns {object} - WebAuthn PublicKeyCredentialRequestOptions
     */
    convertAuthenticationOptions(options) {
        return {
            challenge: this.base64urlToBuffer(options.challenge),
            rpId: options.rpId,
            timeout: options.timeout,
            userVerification: options.userVerification,
            allowCredentials: options.allowCredentials?.map(cred => ({
                id: this.base64urlToBuffer(cred.id),
                type: cred.type,
                transports: cred.transports
            })) || []
        };
    },

    /**
     * Encode credential for sending to server
     * @param {PublicKeyCredential} credential
     * @returns {object}
     */
    encodeCredential(credential) {
        const response = credential.response;

        return {
            id: credential.id,
            rawId: this.bufferToBase64url(credential.rawId),
            type: credential.type,
            response: {
                clientDataJSON: this.bufferToBase64url(response.clientDataJSON),
                attestationObject: this.bufferToBase64url(response.attestationObject),
                transports: response.getTransports ? response.getTransports() : []
            }
        };
    },

    /**
     * Encode authentication credential for sending to server
     * @param {PublicKeyCredential} credential
     * @returns {object}
     */
    encodeAuthCredential(credential) {
        const response = credential.response;

        return {
            id: credential.id,
            rawId: this.bufferToBase64url(credential.rawId),
            type: credential.type,
            response: {
                clientDataJSON: this.bufferToBase64url(response.clientDataJSON),
                authenticatorData: this.bufferToBase64url(response.authenticatorData),
                signature: this.bufferToBase64url(response.signature),
                userHandle: response.userHandle ? this.bufferToBase64url(response.userHandle) : null
            }
        };
    },

    /**
     * Convert string to ArrayBuffer (handles both plain text and base64url)
     * @param {string} str - String to convert
     * @returns {ArrayBuffer}
     */
    stringToBuffer(str) {
        // Check if it looks like base64url (only contains base64url-safe characters)
        if (/^[A-Za-z0-9_-]+$/.test(str) && str.length > 10) {
            // Likely base64url encoded, try to decode
            try {
                return this.base64urlToBuffer(str);
            } catch (e) {
                // If decoding fails, treat as plain text
                console.warn('Failed to decode as base64url, treating as plain text:', e);
            }
        }

        // Plain text - convert to UTF-8 bytes
        const encoder = new TextEncoder();
        return encoder.encode(str).buffer;
    },

    /**
     * Convert base64url string to ArrayBuffer
     * @param {string} base64url
     * @returns {ArrayBuffer}
     */
    base64urlToBuffer(base64url) {
        try {
            // Ensure input is a string and trim whitespace
            if (typeof base64url !== 'string') {
                throw new Error(`Expected string, got ${typeof base64url}`);
            }

            base64url = base64url.trim();

            // Convert base64url to base64
            const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');

            // Add padding
            const padLen = (4 - (base64.length % 4)) % 4;
            const padded = base64 + '='.repeat(padLen);

            // Decode base64 to binary string
            const binary = atob(padded);

            // Convert to ArrayBuffer
            const bytes = new Uint8Array(binary.length);
            for (let i = 0; i < binary.length; i++) {
                bytes[i] = binary.charCodeAt(i);
            }
            return bytes.buffer;
        } catch (error) {
            console.error('base64urlToBuffer error:', error);
            console.error('Input value:', base64url);
            console.error('Input type:', typeof base64url);
            throw new Error(`Failed to decode base64url: ${error.message}`);
        }
    },

    /**
     * Convert ArrayBuffer to base64url string
     * @param {ArrayBuffer} buffer
     * @returns {string}
     */
    bufferToBase64url(buffer) {
        const bytes = new Uint8Array(buffer);
        let binary = '';
        for (let i = 0; i < bytes.byteLength; i++) {
            binary += String.fromCharCode(bytes[i]);
        }
        const base64 = btoa(binary);
        return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
    },

    /**
     * Delete a passkey
     * @param {string} credentialId - Credential ID to delete
     * @param {string} deviceName - Device name for confirmation
     */
    async deletePasskey(credentialId, deviceName) {
        if (!confirm(`Are you sure you want to delete the passkey "${deviceName}"?\n\nThis action cannot be undone.`)) {
            return;
        }

        try {
            const token = localStorage.getItem('authToken') || sessionStorage.getItem('authToken');
            if (!token) {
                showNotification('error', 'Authentication Required', 'Please log in first');
                return;
            }

            const response = await fetch(`/api/auth/passkey/${encodeURIComponent(credentialId)}`, {
                method: 'DELETE',
                headers: {
                    'Authorization': `Bearer ${token}`,
                    'Content-Type': 'application/json'
                }
            });

            if (!response.ok) {
                const error = await response.json();
                throw new Error(error.error || 'Failed to delete passkey');
            }

            showNotification('success', 'Passkey Deleted', `${deviceName} has been removed`);

            // Reload passkey list
            if (typeof loadPasskeyStatus === 'function') {
                loadPasskeyStatus();
            }
            if (typeof listPasskeys === 'function') {
                listPasskeys();
            }

        } catch (error) {
            console.error('Delete passkey error:', error);
            showNotification('error', 'Delete Error', error.message);
        }
    },

    /**
     * Rename a passkey
     * @param {string} credentialId - Credential ID to rename
     * @param {string} currentName - Current device name
     */
    async renamePasskey(credentialId, currentName) {
        const newName = prompt('Enter a new name for this passkey:', currentName);
        if (!newName || newName === currentName) {return;}

        try {
            const token = localStorage.getItem('authToken') || sessionStorage.getItem('authToken');
            if (!token) {
                showNotification('error', 'Authentication Required', 'Please log in first');
                return;
            }

            const response = await fetch(`/api/auth/passkey/${encodeURIComponent(credentialId)}/rename`, {
                method: 'PUT',
                headers: {
                    'Authorization': `Bearer ${token}`,
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ deviceName: newName })
            });

            if (!response.ok) {
                const error = await response.json();
                throw new Error(error.error || 'Failed to rename passkey');
            }

            showNotification('success', 'Passkey Renamed', `Renamed to "${newName}"`);

            // Reload passkey list
            if (typeof listPasskeys === 'function') {
                listPasskeys();
            }

        } catch (error) {
            console.error('Rename passkey error:', error);
            showNotification('error', 'Rename Error', error.message);
        }
    },

    /**
     * Authenticate with passkey
     * @param {string} username - Username (optional for resident keys)
     * @returns {Promise<object>} - Authentication result with token
     */
    async authenticate(username = null) {
        if (!this.isSupported()) {
            this.showNotSupportedError();
            return null;
        }

        try {
            // Get authentication options from server
            const optionsResponse = await fetch('/api/auth/passkey/authenticate/options', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ username })
            });

            if (!optionsResponse.ok) {
                const error = await optionsResponse.json();
                throw new Error(error.error || 'Failed to get authentication options');
            }

            const { options } = await optionsResponse.json();

            // Convert options for WebAuthn API
            const publicKeyOptions = this.convertAuthenticationOptions(options);

            // Prompt user for biometric/security key
            showNotification('info', 'Authenticate', 'Please authenticate with your passkey...');

            let credential;
            try {
                credential = await navigator.credentials.get({
                    publicKey: publicKeyOptions
                });
            } catch (err) {
                if (err.name === 'NotAllowedError') {
                    showNotification('warning', 'Cancelled', 'Authentication was cancelled');
                } else {
                    throw err;
                }
                return null;
            }

            if (!credential) {
                showNotification('error', 'Authentication Failed', 'No credential was provided');
                return null;
            }

            // Send credential to server for verification
            const verificationResponse = await fetch('/api/auth/passkey/authenticate/verify', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    username: username,
                    response: this.encodeAuthCredential(credential)
                })
            });

            if (!verificationResponse.ok) {
                const error = await verificationResponse.json();
                throw new Error(error.error || 'Authentication failed');
            }

            const result = await verificationResponse.json();
            showNotification('success', 'Authenticated', 'Successfully logged in with passkey!');

            return result;

        } catch (error) {
            console.error('Passkey authentication error:', error);
            showNotification('error', 'Authentication Error', error.message);
            return null;
        }
    }
};

// Make globally available
window.Passkeys = Passkeys;
