// SDK State Synchronization — offline/online sync, multi-tab coordination.
// Split: localStorage+BroadcastChannel → sync-storage.ts  |  conflict → sync-conflict.ts

import type { SDKState, SyncState, SyncStatus, ConflictResolution } from '@breakpilot/compliance-sdk-types'
import { ComplianceClient } from './client'
import {
  saveStateToLocalStorage,
  loadStateFromLocalStorage,
  clearStateFromLocalStorage,
  createBroadcastChannel,
  broadcastStateUpdate,
  broadcastSyncComplete,
  setupOnlineListener,
} from './sync-storage'
import { defaultConflictHandler, applyConflictResolution } from './sync-conflict'

// --- Types & Constants -------------------------------------------------------

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
}

const DEFAULT_DEBOUNCE_MS = 2000
const DEFAULT_MAX_RETRIES = 3

// --- StateSyncManager --------------------------------------------------------

export class StateSyncManager {
  private client: ComplianceClient
  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 = true

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

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

    this.setupBroadcastChannel()
    this.setupOnlineListener()
  }

  // -- Setup --

  private setupBroadcastChannel(): void {
    this.broadcastChannel = createBroadcastChannel(this.tenantId)
    if (this.broadcastChannel) {
      this.broadcastChannel.onmessage = this.handleBroadcastMessage.bind(this)
    }
  }

  private setupOnlineListener(): void {
    this.isOnline = setupOnlineListener({
      onOnline: () => {
        this.isOnline = true
        this.syncState.status = 'idle'
        this.callbacks.onOnline?.()
        if (this.pendingState) this.syncToServer(this.pendingState)
      },
      onOffline: () => {
        this.isOnline = false
        this.syncState.status = 'offline'
        this.callbacks.onOffline?.()
      },
    })
    if (!this.isOnline) this.syncState.status = 'offline'
  }

  // -- Broadcast Channel --

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

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

      case 'SYNC_COMPLETE':
        this.syncState.serverVersion = version
        break

      case 'REQUEST_STATE':
        this.broadcastCurrentState()
        break
    }
  }

  private broadcastCurrentState(): void {
    const state = this.loadFromLocalStorage()
    if (state) {
      broadcastStateUpdate(this.broadcastChannel, state, this.syncState.localVersion)
    }
  }

  // -- Local Storage (sync-storage helpers) --

  saveToLocalStorage(state: SDKState): void {
    saveStateToLocalStorage(this.tenantId, state, this.syncState.localVersion)
  }

  loadFromLocalStorage(): SDKState | null {
    return loadStateFromLocalStorage(this.tenantId, this.syncState)
  }

  clearLocalStorage(): void {
    clearStateFromLocalStorage(this.tenantId)
  }

  // -- Sync --

  queueSync(state: SDKState): void {
    this.pendingState = state
    this.syncState.pendingChanges++

    this.syncState.localVersion++
    this.saveToLocalStorage(state)
    broadcastStateUpdate(this.broadcastChannel, state, this.syncState.localVersion)

    if (this.debounceTimeout) {
      clearTimeout(this.debounceTimeout)
    }

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

  async forceSync(state: SDKState): Promise<void> {
    if (this.debounceTimeout) {
      clearTimeout(this.debounceTimeout)
      this.debounceTimeout = null
    }

    await this.syncToServer(state)
  }

  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.client.saveState(state, this.syncState.serverVersion)

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

      this.pendingState = null
      broadcastSyncComplete(this.broadcastChannel, response.version)
      this.callbacks.onSyncComplete?.(state)
    } catch (error) {
      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)
      }
    }
  }

  async loadFromServer(): Promise<SDKState | null> {
    if (!this.isOnline) {
      return this.loadFromLocalStorage()
    }

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

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

      return this.loadFromLocalStorage()
    } catch (error) {
      console.error('Failed to load from server:', error)
      return this.loadFromLocalStorage()
    }
  }

  // -- Conflict Resolution (sync-conflict helpers) --

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

    try {
      const serverResponse = await this.client.getState()

      if (!serverResponse) {
        await this.client.saveState(localState)
        return
      }

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

      const resolution = await this.options.conflictHandler(localState, serverState)
      const resolvedState = applyConflictResolution(resolution, localState, serverState)

      const response = await this.client.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)
    }
  }

  // -- Getters & Cleanup --

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

  isOnlineStatus(): boolean {
    return this.isOnline
  }

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

  destroy(): void {
    if (this.debounceTimeout) {
      clearTimeout(this.debounceTimeout)
    }

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

// --- Factory -----------------------------------------------------------------

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