/**
 * EncryptID Social Recovery Module
 *
 * Implements guardian-based account recovery with NO SEED PHRASES.
 * This is Layer 4 of the EncryptID architecture.
 */

import { bufferToBase64url, base64urlToBuffer } from './webauthn';

// ============================================================================
// TYPES
// ============================================================================

/**
 * Types of guardians that can be added
 */
export enum GuardianType {
  /** Another passkey owned by the user (e.g., YubiKey backup) */
  SECONDARY_PASSKEY = 'secondary_passkey',

  /** A trusted contact with their own EncryptID */
  TRUSTED_CONTACT = 'trusted_contact',

  /** A hardware security key stored offline */
  HARDWARE_KEY = 'hardware_key',

  /** An institutional guardian (service provider) */
  INSTITUTIONAL = 'institutional',

  /** Time-delayed self-recovery (requires waiting period) */
  TIME_DELAYED_SELF = 'time_delayed_self',
}

/**
 * Guardian configuration
 */
export interface Guardian {
  id: string;
  type: GuardianType;
  name: string;
  weight: number;  // Contribution to threshold (usually 1)

  // Type-specific data
  credentialId?: string;        // For SECONDARY_PASSKEY
  contactDID?: string;          // For TRUSTED_CONTACT
  contactEmail?: string;        // For notification
  serviceUrl?: string;          // For INSTITUTIONAL
  delaySeconds?: number;        // For TIME_DELAYED_SELF

  // Metadata
  addedAt: number;
  lastVerified?: number;
}

/**
 * Recovery configuration for an account
 */
export interface RecoveryConfig {
  /** Required weight to recover (e.g., 3 for 3-of-5) */
  threshold: number;

  /** Time-lock delay in seconds before recovery completes */
  delaySeconds: number;

  /** List of guardians */
  guardians: Guardian[];

  /** Hash of guardian addresses (for privacy) */
  guardianListHash: string;

  /** When config was last updated */
  updatedAt: number;
}

/**
 * Active recovery request
 */
export interface RecoveryRequest {
  id: string;
  accountDID: string;
  newCredentialId: string;
  initiatedAt: number;
  completesAt: number;
  status: 'pending' | 'approved' | 'cancelled' | 'completed';

  /** Guardians who have approved */
  approvals: {
    guardianId: string;
    approvedAt: number;
    signature: string;
  }[];

  /** Total weight of approvals */
  approvalWeight: number;
}

// ============================================================================
// GUARDIAN MANAGEMENT
// ============================================================================

/**
 * EncryptID Recovery Manager
 *
 * Handles guardian configuration and recovery flows.
 */
export class RecoveryManager {
  private config: RecoveryConfig | null = null;
  private activeRequest: RecoveryRequest | null = null;
  private loaded = false;

  constructor() {
    this.loadLocalSettings();
  }

  /** Get the auth token for server API calls */
  private getAuthToken(): string | null {
    try {
      return localStorage.getItem('encryptid_token');
    } catch {
      return null;
    }
  }

  /** Resolve EncryptID API base URL */
  private getApiBase(): string {
    // When running on the encryptid domain, use relative paths
    if (typeof location !== 'undefined' && location.hostname.includes('auth.')) {
      return '';
    }
    // From rspace canvas, reach encryptid via its public URL
    return 'https://auth.rspace.online';
  }

  /**
   * Initialize recovery with default configuration.
   * Fetches existing guardians from server if authenticated.
   */
  async initializeRecovery(threshold: number = 3): Promise<RecoveryConfig> {
    this.config = {
      threshold,
      delaySeconds: 48 * 60 * 60,  // 48 hours
      guardians: [],
      guardianListHash: '',
      updatedAt: Date.now(),
    };

    // Try to load existing guardians from server
    await this.syncFromServer();

    await this.saveLocalSettings();
    return this.config;
  }

  /**
   * Ensure config is loaded (call before operations that need config)
   */
  async ensureLoaded(): Promise<void> {
    if (this.loaded) return;
    await this.syncFromServer();
    this.loaded = true;
  }

  /**
   * Add a guardian — persists to server API (which sends invite email).
   * Falls back to local-only if not authenticated.
   */
  async addGuardian(guardian: Omit<Guardian, 'id' | 'addedAt'>): Promise<Guardian> {
    if (!this.config) {
      throw new Error('Recovery not initialized');
    }

    if (this.config.guardians.length >= 3) {
      throw new Error('Maximum of 3 guardians allowed. Remove one first.');
    }

    const token = this.getAuthToken();
    let serverId: string | undefined;

    // POST to server — creates DB row + sends invite email
    if (token) {
      const res = await fetch(`${this.getApiBase()}/api/guardians`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${token}`,
        },
        body: JSON.stringify({
          name: guardian.name,
          email: guardian.contactEmail || undefined,
        }),
      });

      if (!res.ok) {
        const err = await res.json().catch(() => ({ error: 'Server error' }));
        throw new Error(err.error || `Server error: ${res.status}`);
      }

      const data = await res.json().catch(() => ({}));
      serverId = data.guardian?.id;

      console.log('EncryptID: Guardian added via server', {
        id: serverId,
        inviteUrl: data.inviteUrl,
      });
    }

    const newGuardian: Guardian = {
      ...guardian,
      id: serverId || bufferToBase64url(crypto.getRandomValues(new Uint8Array(16)).buffer),
      addedAt: Date.now(),
    };

    this.config.guardians.push(newGuardian);
    this.config.guardianListHash = await this.hashGuardianList();
    this.config.updatedAt = Date.now();

    await this.saveLocalSettings();

    console.log('EncryptID: Guardian added', {
      type: GuardianType[guardian.type],
      name: guardian.name,
    });

    return newGuardian;
  }

  /**
   * Remove a guardian — deletes from server API and local config.
   */
  async removeGuardian(guardianId: string): Promise<void> {
    if (!this.config) {
      throw new Error('Recovery not initialized');
    }

    const index = this.config.guardians.findIndex(g => g.id === guardianId);
    if (index === -1) {
      throw new Error('Guardian not found');
    }

    // DELETE from server
    const token = this.getAuthToken();
    if (token) {
      const res = await fetch(`${this.getApiBase()}/api/guardians/${guardianId}`, {
        method: 'DELETE',
        headers: { 'Authorization': `Bearer ${token}` },
      });

      if (!res.ok && res.status !== 404) {
        const err = await res.json().catch(() => ({ error: 'Server error' }));
        throw new Error(err.error || `Server error: ${res.status}`);
      }
    }

    this.config.guardians.splice(index, 1);
    this.config.guardianListHash = await this.hashGuardianList();
    this.config.updatedAt = Date.now();

    await this.saveLocalSettings();

    console.log('EncryptID: Guardian removed');
  }

  /**
   * Update recovery threshold
   */
  async setThreshold(threshold: number): Promise<void> {
    if (!this.config) {
      throw new Error('Recovery not initialized');
    }

    const totalWeight = this.config.guardians.reduce((sum, g) => sum + g.weight, 0);
    if (threshold > totalWeight) {
      throw new Error('Threshold cannot exceed total guardian weight');
    }

    if (threshold < 1) {
      throw new Error('Threshold must be at least 1');
    }

    this.config.threshold = threshold;
    this.config.updatedAt = Date.now();

    await this.saveLocalSettings();

    console.log('EncryptID: Threshold updated to', threshold);
  }

  /**
   * Set recovery time-lock delay
   */
  async setDelay(delaySeconds: number): Promise<void> {
    if (!this.config) {
      throw new Error('Recovery not initialized');
    }

    // Minimum 1 hour, maximum 7 days
    if (delaySeconds < 3600 || delaySeconds > 7 * 24 * 3600) {
      throw new Error('Delay must be between 1 hour and 7 days');
    }

    this.config.delaySeconds = delaySeconds;
    this.config.updatedAt = Date.now();

    await this.saveLocalSettings();

    console.log('EncryptID: Delay updated to', delaySeconds, 'seconds');
  }

  /**
   * Get current configuration
   */
  getConfig(): RecoveryConfig | null {
    return this.config;
  }

  /**
   * Check if recovery is properly configured
   */
  isConfigured(): boolean {
    if (!this.config) return false;

    const totalWeight = this.config.guardians.reduce((sum, g) => sum + g.weight, 0);
    return totalWeight >= this.config.threshold;
  }

  /**
   * Verify a guardian is still reachable/valid.
   * Checks server for current guardian status (pending/accepted).
   */
  async verifyGuardian(guardianId: string): Promise<boolean> {
    if (!this.config) {
      throw new Error('Recovery not initialized');
    }

    const guardian = this.config.guardians.find(g => g.id === guardianId);
    if (!guardian) {
      throw new Error('Guardian not found');
    }

    const token = this.getAuthToken();
    if (token) {
      // Fetch current guardian list from server to check status
      const res = await fetch(`${this.getApiBase()}/api/guardians`, {
        headers: { 'Authorization': `Bearer ${token}` },
      });

      if (res.ok) {
        const data = await res.json().catch(() => ({ guardians: [] }));
        const serverGuardian = data.guardians?.find((g: any) => g.id === guardianId);

        if (!serverGuardian) {
          throw new Error('Guardian not found on server — may have been removed');
        }

        // Check acceptance status
        if (serverGuardian.status === 'accepted') {
          guardian.lastVerified = Date.now();
          await this.saveLocalSettings();
          return true;
        }

        // Still pending — guardian hasn't accepted invite yet
        if (serverGuardian.status === 'pending') {
          console.log('EncryptID: Guardian invite still pending', { guardianId });
          return false;
        }
      }
    }

    // Fallback: mark as verified locally (offline mode)
    guardian.lastVerified = Date.now();
    await this.saveLocalSettings();
    return true;
  }

  // ==========================================================================
  // RECOVERY FLOW
  // ==========================================================================

  /**
   * Initiate account recovery
   *
   * This starts the recovery process. Guardians must approve,
   * and there's a time-lock before recovery completes.
   */
  async initiateRecovery(newCredentialId: string): Promise<RecoveryRequest> {
    if (!this.config) {
      throw new Error('Recovery not configured');
    }

    if (this.activeRequest && this.activeRequest.status === 'pending') {
      throw new Error('Recovery already in progress');
    }

    const now = Date.now();

    this.activeRequest = {
      id: bufferToBase64url(crypto.getRandomValues(new Uint8Array(16)).buffer),
      accountDID: '', // Would be set from current session
      newCredentialId,
      initiatedAt: now,
      completesAt: now + this.config.delaySeconds * 1000,
      status: 'pending',
      approvals: [],
      approvalWeight: 0,
    };

    // Notify user through all available channels
    await this.notifyUser('recovery_initiated', this.activeRequest);

    // Notify guardians
    await this.notifyGuardians('recovery_requested', this.activeRequest);

    console.log('EncryptID: Recovery initiated', {
      requestId: this.activeRequest.id,
      completesAt: new Date(this.activeRequest.completesAt).toISOString(),
    });

    return this.activeRequest;
  }

  /**
   * Guardian approves a recovery request
   */
  async approveRecovery(
    guardianId: string,
    signature: string
  ): Promise<RecoveryRequest> {
    if (!this.activeRequest || this.activeRequest.status !== 'pending') {
      throw new Error('No pending recovery request');
    }

    if (!this.config) {
      throw new Error('Recovery not configured');
    }

    const guardian = this.config.guardians.find(g => g.id === guardianId);
    if (!guardian) {
      throw new Error('Guardian not found');
    }

    // Check if guardian already approved
    if (this.activeRequest.approvals.some(a => a.guardianId === guardianId)) {
      throw new Error('Guardian already approved');
    }

    // Add approval
    this.activeRequest.approvals.push({
      guardianId,
      approvedAt: Date.now(),
      signature,
    });

    this.activeRequest.approvalWeight += guardian.weight;

    // Check if threshold reached
    if (this.activeRequest.approvalWeight >= this.config.threshold) {
      this.activeRequest.status = 'approved';
      await this.notifyUser('recovery_approved', this.activeRequest);
    }

    console.log('EncryptID: Guardian approved recovery', {
      guardianId,
      weight: guardian.weight,
      totalWeight: this.activeRequest.approvalWeight,
      threshold: this.config.threshold,
    });

    return this.activeRequest;
  }

  /**
   * Cancel an active recovery request
   *
   * User can cancel if they still have access to any valid authenticator.
   */
  async cancelRecovery(): Promise<void> {
    if (!this.activeRequest || this.activeRequest.status !== 'pending') {
      throw new Error('No pending recovery request to cancel');
    }

    this.activeRequest.status = 'cancelled';

    // Notify guardians
    await this.notifyGuardians('recovery_cancelled', this.activeRequest);

    console.log('EncryptID: Recovery cancelled');

    this.activeRequest = null;
  }

  /**
   * Complete the recovery process
   *
   * Can only be called after time-lock expires and threshold is met.
   */
  async completeRecovery(): Promise<void> {
    if (!this.activeRequest) {
      throw new Error('No recovery request');
    }

    if (this.activeRequest.status !== 'approved') {
      throw new Error('Recovery not approved');
    }

    if (Date.now() < this.activeRequest.completesAt) {
      const remaining = this.activeRequest.completesAt - Date.now();
      throw new Error(`Time-lock not expired. ${Math.ceil(remaining / 1000 / 60)} minutes remaining.`);
    }

    // In production, this would:
    // 1. Rotate the account owner to the new credential
    // 2. Invalidate old credentials
    // 3. Update on-chain state (for AA wallet)

    this.activeRequest.status = 'completed';

    console.log('EncryptID: Recovery completed successfully');

    // Notify user
    await this.notifyUser('recovery_completed', this.activeRequest);

    this.activeRequest = null;
  }

  /**
   * Get active recovery request
   */
  getActiveRequest(): RecoveryRequest | null {
    return this.activeRequest;
  }

  // ==========================================================================
  // PRIVATE METHODS
  // ==========================================================================

  /**
   * Hash guardian list for privacy-preserving on-chain storage
   */
  private async hashGuardianList(): Promise<string> {
    if (!this.config) return '';

    // Sort guardian IDs for deterministic hash
    const sortedIds = this.config.guardians
      .map(g => g.id)
      .sort()
      .join(',');

    const encoder = new TextEncoder();
    const hash = await crypto.subtle.digest('SHA-256', encoder.encode(sortedIds));

    return bufferToBase64url(hash);
  }

  /**
   * Save local settings (threshold, delay) to localStorage.
   * Guardian CRUD goes through the server API — this only persists
   * config that the server doesn't track (threshold, delay, type metadata).
   */
  private async saveLocalSettings(): Promise<void> {
    if (!this.config) return;

    try {
      localStorage.setItem('encryptid_recovery', JSON.stringify(this.config));
    } catch (error) {
      console.warn('EncryptID: Failed to save recovery config', error);
    }
  }

  /**
   * Load local settings from localStorage (synchronous, for constructor).
   * Call syncFromServer() after construction for full server sync.
   */
  private loadLocalSettings(): void {
    try {
      const stored = localStorage.getItem('encryptid_recovery');
      if (stored) {
        this.config = JSON.parse(stored);
      }
    } catch (error) {
      console.warn('EncryptID: Failed to load recovery config', error);
    }
  }

  /**
   * Sync guardian list from server API into local config.
   * Maps server guardian model to client Guardian type.
   */
  private async syncFromServer(): Promise<void> {
    const token = this.getAuthToken();
    if (!token) return;

    try {
      const res = await fetch(`${this.getApiBase()}/api/guardians`, {
        headers: { 'Authorization': `Bearer ${token}` },
      });

      if (!res.ok) return;

      const data = await res.json();
      if (!data.guardians || !Array.isArray(data.guardians)) return;

      if (!this.config) {
        this.config = {
          threshold: data.threshold || 2,
          delaySeconds: 48 * 60 * 60,
          guardians: [],
          guardianListHash: '',
          updatedAt: Date.now(),
        };
      }

      // Build a map of existing local guardians for metadata preservation
      const localMap = new Map(this.config.guardians.map(g => [g.id, g]));

      // Merge server guardians with local type metadata
      this.config.guardians = data.guardians.map((sg: any) => {
        const local = localMap.get(sg.id);
        return {
          id: sg.id,
          type: local?.type ?? GuardianType.TRUSTED_CONTACT,
          name: sg.name,
          weight: local?.weight ?? 1,
          contactEmail: sg.email || local?.contactEmail,
          addedAt: sg.createdAt ? new Date(sg.createdAt).getTime() : (local?.addedAt ?? Date.now()),
          lastVerified: sg.status === 'accepted' ? (sg.acceptedAt ? new Date(sg.acceptedAt).getTime() : Date.now()) : local?.lastVerified,
          // Preserve type-specific fields from local
          credentialId: local?.credentialId,
          contactDID: local?.contactDID,
          serviceUrl: local?.serviceUrl,
          delaySeconds: local?.delaySeconds,
        } satisfies Guardian;
      });

      this.config.guardianListHash = await this.hashGuardianList();
      this.config.updatedAt = Date.now();

      await this.saveLocalSettings();

      console.log('EncryptID: Synced guardians from server', {
        count: this.config.guardians.length,
      });
    } catch (error) {
      console.warn('EncryptID: Failed to sync guardians from server', error);
    }
  }

  /**
   * Notify user of recovery events via CustomEvent on document.
   */
  private async notifyUser(
    event: string,
    request: RecoveryRequest
  ): Promise<void> {
    console.log('EncryptID: User notification', { event, requestId: request.id });

    if (typeof document !== 'undefined') {
      document.dispatchEvent(new CustomEvent('encryptid:recovery', {
        detail: { event, requestId: request.id, status: request.status },
      }));
    }
  }

  /**
   * Notify guardians of recovery events via CustomEvent on document.
   * Server-side email notifications are handled by the guardian invite flow.
   */
  private async notifyGuardians(
    event: string,
    request: RecoveryRequest
  ): Promise<void> {
    if (!this.config) return;

    console.log('EncryptID: Guardian notification', {
      event,
      guardianCount: this.config.guardians.length,
    });

    if (typeof document !== 'undefined') {
      document.dispatchEvent(new CustomEvent('encryptid:guardian-notification', {
        detail: {
          event,
          requestId: request.id,
          guardianIds: this.config.guardians.map(g => g.id),
        },
      }));
    }
  }
}

// ============================================================================
// SINGLETON INSTANCE
// ============================================================================

let recoveryManagerInstance: RecoveryManager | null = null;

/**
 * Get the global recovery manager instance
 */
export function getRecoveryManager(): RecoveryManager {
  if (!recoveryManagerInstance) {
    recoveryManagerInstance = new RecoveryManager();
  }
  return recoveryManagerInstance;
}

// ============================================================================
// GUARDIAN TYPE METADATA
// ============================================================================

/**
 * Get display information for guardian types
 */
export function getGuardianTypeInfo(type: GuardianType): {
  name: string;
  description: string;
  icon: string;
  setupInstructions: string;
} {
  switch (type) {
    case GuardianType.SECONDARY_PASSKEY:
      return {
        name: 'Backup Passkey',
        description: 'Another device you own (phone, YubiKey, etc.)',
        icon: 'key',
        setupInstructions: 'Register a passkey on a second device you control. Store it securely as a backup.',
      };

    case GuardianType.TRUSTED_CONTACT:
      return {
        name: 'Trusted Contact',
        description: 'A friend or family member with their own EncryptID',
        icon: 'user',
        setupInstructions: 'Ask a trusted person to create an EncryptID account. They can help recover your account if needed.',
      };

    case GuardianType.HARDWARE_KEY:
      return {
        name: 'Hardware Security Key',
        description: 'A YubiKey or similar device stored offline',
        icon: 'shield',
        setupInstructions: 'Register a hardware security key and store it in a safe place (e.g., safe deposit box).',
      };

    case GuardianType.INSTITUTIONAL:
      return {
        name: 'Recovery Service',
        description: 'A professional recovery service provider',
        icon: 'building',
        setupInstructions: 'Connect with a trusted recovery service that can help verify your identity.',
      };

    case GuardianType.TIME_DELAYED_SELF:
      return {
        name: 'Time-Delayed Self',
        description: 'Recover yourself after a waiting period',
        icon: 'clock',
        setupInstructions: 'Set up a recovery option that requires waiting (e.g., 7 days) before completing.',
      };

    default:
      return {
        name: 'Unknown',
        description: 'Unknown guardian type',
        icon: 'question',
        setupInstructions: '',
      };
  }
}
