/**
 * SDK State Synchronization
 *
 * Handles offline/online sync, multi-tab coordination,
 * and conflict resolution for SDK state.
 */

import { SDKState } from './types'
import { SDKApiClient, StateResponse } from './api-client'

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

export type SyncStatus = 'idle' | 'syncing' | 'error' | 'offline' | 'conflict'

export interface SyncState {
  status: SyncStatus
  lastSyncedAt: Date | null
  localVersion: number
  serverVersion: number
  pendingChanges: number
  error: string | null
}

export interface ConflictResolution {
  strategy: 'local' | 'server' | 'merge'
  mergedState?: SDKState
}

export interface SyncOptions {
  debounceMs?: number
  maxRetries?: number
  conflictHandler?: (local: SDKState, server: SDKState) => Promise<ConflictResolution>
}

export interface SyncCallbacks {
  onSyncStart?: () => void
  onSyncComplete?: (state: SDKState) => void
  onSyncError?: (error: Error) => void
  onConflict?: (local: SDKState, server: SDKState) => void
  onOffline?: () => void
  onOnline?: () => void
}

// =============================================================================
// CONSTANTS
// =============================================================================

const STORAGE_KEY_PREFIX = 'ai-compliance-sdk-state'
const SYNC_CHANNEL = 'sdk-state-sync'
const DEFAULT_DEBOUNCE_MS = 2000
const DEFAULT_MAX_RETRIES = 3

// =============================================================================
// STATE SYNC MANAGER
// =============================================================================

export class StateSyncManager {
  private apiClient: SDKApiClient
  private tenantId: string
  private options: Required<SyncOptions>
  private callbacks: SyncCallbacks
  private syncState: SyncState
  private broadcastChannel: BroadcastChannel | null = null
  private debounceTimeout: ReturnType<typeof setTimeout> | null = null
  private pendingState: SDKState | null = null
  private isOnline: boolean = true

  constructor(
    apiClient: SDKApiClient,
    tenantId: string,
    options: SyncOptions = {},
    callbacks: SyncCallbacks = {}
  ) {
    this.apiClient = apiClient
    this.tenantId = tenantId
    this.callbacks = callbacks
    this.options = {
      debounceMs: options.debounceMs ?? DEFAULT_DEBOUNCE_MS,
      maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
      conflictHandler: options.conflictHandler ?? this.defaultConflictHandler,
    }

    this.syncState = {
      status: 'idle',
      lastSyncedAt: null,
      localVersion: 0,
      serverVersion: 0,
      pendingChanges: 0,
      error: null,
    }

    this.setupBroadcastChannel()
    this.setupOnlineListener()
  }

  // ---------------------------------------------------------------------------
  // Setup Methods
  // ---------------------------------------------------------------------------

  private setupBroadcastChannel(): void {
    if (typeof window === 'undefined' || !('BroadcastChannel' in window)) {
      return
    }

    try {
      this.broadcastChannel = new BroadcastChannel(`${SYNC_CHANNEL}-${this.tenantId}`)
      this.broadcastChannel.onmessage = this.handleBroadcastMessage.bind(this)
    } catch (error) {
      console.warn('BroadcastChannel not available:', error)
    }
  }

  private setupOnlineListener(): void {
    if (typeof window === 'undefined') {
      return
    }

    window.addEventListener('online', () => {
      this.isOnline = true
      this.syncState.status = 'idle'
      this.callbacks.onOnline?.()
      // Attempt to sync any pending changes
      if (this.pendingState) {
        this.syncToServer(this.pendingState)
      }
    })

    window.addEventListener('offline', () => {
      this.isOnline = false
      this.syncState.status = 'offline'
      this.callbacks.onOffline?.()
    })

    // Check initial online status
    this.isOnline = navigator.onLine
    if (!this.isOnline) {
      this.syncState.status = 'offline'
    }
  }

  // ---------------------------------------------------------------------------
  // Broadcast Channel Methods
  // ---------------------------------------------------------------------------

  private handleBroadcastMessage(event: MessageEvent): void {
    const { type, state, version, tabId } = event.data

    switch (type) {
      case 'STATE_UPDATED':
        // Another tab updated the state
        if (version > this.syncState.localVersion) {
          this.syncState.localVersion = version
          this.saveToLocalStorage(state)
          this.callbacks.onSyncComplete?.(state)
        }
        break

      case 'SYNC_COMPLETE':
        // Another tab completed a sync
        this.syncState.serverVersion = version
        break

      case 'REQUEST_STATE':
        // Another tab is requesting the current state
        this.broadcastState()
        break
    }
  }

  private broadcastState(): void {
    if (!this.broadcastChannel) return

    const state = this.loadFromLocalStorage()
    if (state) {
      this.broadcastChannel.postMessage({
        type: 'STATE_UPDATED',
        state,
        version: this.syncState.localVersion,
        tabId: this.getTabId(),
      })
    }
  }

  private broadcastSyncComplete(version: number): void {
    if (!this.broadcastChannel) return

    this.broadcastChannel.postMessage({
      type: 'SYNC_COMPLETE',
      version,
      tabId: this.getTabId(),
    })
  }

  private getTabId(): string {
    if (typeof window === 'undefined') return 'server'

    let tabId = sessionStorage.getItem('sdk-tab-id')
    if (!tabId) {
      tabId = `tab-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
      sessionStorage.setItem('sdk-tab-id', tabId)
    }
    return tabId
  }

  // ---------------------------------------------------------------------------
  // Local Storage Methods
  // ---------------------------------------------------------------------------

  private getStorageKey(): string {
    return `${STORAGE_KEY_PREFIX}-${this.tenantId}`
  }

  saveToLocalStorage(state: SDKState): void {
    if (typeof window === 'undefined') return

    try {
      const data = {
        state,
        version: this.syncState.localVersion,
        savedAt: new Date().toISOString(),
      }
      localStorage.setItem(this.getStorageKey(), JSON.stringify(data))
    } catch (error) {
      console.error('Failed to save to localStorage:', error)
    }
  }

  loadFromLocalStorage(): SDKState | null {
    if (typeof window === 'undefined') return null

    try {
      const stored = localStorage.getItem(this.getStorageKey())
      if (stored) {
        const data = JSON.parse(stored)
        this.syncState.localVersion = data.version || 0
        return data.state
      }
    } catch (error) {
      console.error('Failed to load from localStorage:', error)
    }
    return null
  }

  clearLocalStorage(): void {
    if (typeof window === 'undefined') return

    try {
      localStorage.removeItem(this.getStorageKey())
    } catch (error) {
      console.error('Failed to clear localStorage:', error)
    }
  }

  // ---------------------------------------------------------------------------
  // Sync Methods
  // ---------------------------------------------------------------------------

  /**
   * Queue a state change for syncing (debounced)
   */
  queueSync(state: SDKState): void {
    this.pendingState = state
    this.syncState.pendingChanges++

    // Save to localStorage immediately
    this.syncState.localVersion++
    this.saveToLocalStorage(state)

    // Broadcast to other tabs
    this.broadcastState()

    // Debounce server sync
    if (this.debounceTimeout) {
      clearTimeout(this.debounceTimeout)
    }

    this.debounceTimeout = setTimeout(() => {
      this.syncToServer(state)
    }, this.options.debounceMs)
  }

  /**
   * Force immediate sync to server
   */
  async forcSync(state: SDKState): Promise<void> {
    if (this.debounceTimeout) {
      clearTimeout(this.debounceTimeout)
      this.debounceTimeout = null
    }

    await this.syncToServer(state)
  }

  /**
   * Sync state to server
   */
  private async syncToServer(state: SDKState): Promise<void> {
    if (!this.isOnline) {
      this.syncState.status = 'offline'
      return
    }

    this.syncState.status = 'syncing'
    this.callbacks.onSyncStart?.()

    try {
      const response = await this.apiClient.saveState(state, this.syncState.serverVersion)

      this.syncState = {
        ...this.syncState,
        status: 'idle',
        lastSyncedAt: new Date(),
        serverVersion: response.version,
        pendingChanges: 0,
        error: null,
      }

      this.pendingState = null
      this.broadcastSyncComplete(response.version)
      this.callbacks.onSyncComplete?.(state)
    } catch (error) {
      // Handle version conflict (409)
      if ((error as { status?: number }).status === 409) {
        await this.handleConflict(state)
      } else {
        this.syncState.status = 'error'
        this.syncState.error = (error as Error).message
        this.callbacks.onSyncError?.(error as Error)
      }
    }
  }

  /**
   * Load state from server
   */
  async loadFromServer(): Promise<SDKState | null> {
    if (!this.isOnline) {
      return this.loadFromLocalStorage()
    }

    try {
      const response = await this.apiClient.getState()

      if (response) {
        this.syncState.serverVersion = response.version
        this.syncState.localVersion = response.version
        this.saveToLocalStorage(response.state)
        return response.state
      }

      // No server state, return local if available
      return this.loadFromLocalStorage()
    } catch (error) {
      console.error('Failed to load from server:', error)
      // Fallback to local storage
      return this.loadFromLocalStorage()
    }
  }

  // ---------------------------------------------------------------------------
  // Conflict Resolution
  // ---------------------------------------------------------------------------

  private async handleConflict(localState: SDKState): Promise<void> {
    this.syncState.status = 'conflict'

    try {
      // Fetch server state
      const serverResponse = await this.apiClient.getState()

      if (!serverResponse) {
        // Server has no state, use local
        await this.apiClient.saveState(localState)
        return
      }

      const serverState = serverResponse.state
      this.callbacks.onConflict?.(localState, serverState)

      // Resolve conflict
      const resolution = await this.options.conflictHandler(localState, serverState)

      let resolvedState: SDKState
      switch (resolution.strategy) {
        case 'local':
          resolvedState = localState
          break
        case 'server':
          resolvedState = serverState
          break
        case 'merge':
          resolvedState = resolution.mergedState || localState
          break
      }

      // Save resolved state
      const response = await this.apiClient.saveState(resolvedState)
      this.syncState.serverVersion = response.version
      this.syncState.localVersion = response.version
      this.saveToLocalStorage(resolvedState)
      this.syncState.status = 'idle'
      this.callbacks.onSyncComplete?.(resolvedState)
    } catch (error) {
      this.syncState.status = 'error'
      this.syncState.error = (error as Error).message
      this.callbacks.onSyncError?.(error as Error)
    }
  }

  private async defaultConflictHandler(
    local: SDKState,
    server: SDKState
  ): Promise<ConflictResolution> {
    // Default: Server wins, but we preserve certain local-only data
    const localTime = new Date(local.lastModified).getTime()
    const serverTime = new Date(server.lastModified).getTime()

    if (localTime > serverTime) {
      // Local is newer, use local
      return { strategy: 'local' }
    }

    // Merge: Use server state but preserve local UI preferences
    const mergedState: SDKState = {
      ...server,
      preferences: local.preferences,
      commandBarHistory: [
        ...local.commandBarHistory,
        ...server.commandBarHistory.filter(
          h => !local.commandBarHistory.some(lh => lh.id === h.id)
        ),
      ].slice(0, 50),
      recentSearches: [...new Set([...local.recentSearches, ...server.recentSearches])].slice(0, 20),
    }

    return { strategy: 'merge', mergedState }
  }

  // ---------------------------------------------------------------------------
  // Getters & Cleanup
  // ---------------------------------------------------------------------------

  getSyncState(): SyncState {
    return { ...this.syncState }
  }

  isOnlineStatus(): boolean {
    return this.isOnline
  }

  hasPendingChanges(): boolean {
    return this.syncState.pendingChanges > 0 || this.pendingState !== null
  }

  /**
   * Cleanup resources
   */
  destroy(): void {
    if (this.debounceTimeout) {
      clearTimeout(this.debounceTimeout)
    }

    if (this.broadcastChannel) {
      this.broadcastChannel.close()
    }
  }
}

// =============================================================================
// FACTORY FUNCTION
// =============================================================================

export function createStateSyncManager(
  apiClient: SDKApiClient,
  tenantId: string,
  options?: SyncOptions,
  callbacks?: SyncCallbacks
): StateSyncManager {
  return new StateSyncManager(apiClient, tenantId, options, callbacks)
}
