import { createInitialState, applyAction } from './gameState.js'

/**
 * Crea e configura il server WebSocket per la gestione dello stato di gioco.
 * @param {WebSocketServer} wss - Istanza del server WebSocket.
 * @returns {Object} Oggetto con metodi di gestione dello stato.
 */
const DURATA_TIMEOUT_MS = 30000

export function setupWebSocketHandler(wss, options = {}) {
  // Stato globale della partita.
  let gameState = options.initialState ?? createInitialState()

  // Mappa dei ruoli associati ai client connessi.
  const clients = new Map() // ws -> { role: 'display' | 'controller' }

  // Timer che chiude automaticamente il time out dopo 30s, indipendentemente
  // dal controller (sopravvive a refresh/disconnessione/standby del tablet).
  let timeoutTimer = null

  function annullaTimerTimeout() {
    if (timeoutTimer) {
      clearTimeout(timeoutTimer)
      timeoutTimer = null
    }
  }

  /**
   * Gestisce i messaggi in arrivo dal client
   */
  function handleMessage(ws, data) {
    try {
      // Converte il payload in stringa in modo sicuro, anche se arriva come Buffer.
      const dataStr = typeof data === 'string' ? data : data.toString('utf8')
      const msg = JSON.parse(dataStr)

      if (msg.type === 'register') {
        return handleRegister(ws, msg)
      }

      if (msg.type === 'action') {
        return handleAction(ws, msg)
      }

      if (msg.type === 'speak') {
        return handleSpeak(ws, msg)
      }
    } catch (err) {
      console.error('Error processing message:', err, 'data:', data)
      // Invia l'errore solo se la connessione e ancora aperta.
      if (ws.readyState === 1) { // Stato WebSocket.OPEN
        try {
          sendError(ws, 'Invalid message format')
        } catch (sendErr) {
          console.error('Error sending error message:', sendErr)
        }
      }
    }
  }

  /**
   * Gestisce la registrazione di un client (display o controller)
   */
  function handleRegister(ws, msg) {
    const role = msg.role || 'display'

    // Valida il ruolo dichiarato dal client.
    if (!['display', 'controller'].includes(role)) {
      sendError(ws, 'Invalid role')
      return
    }

    clients.set(ws, { role })
    console.log(`[WebSocket] Client registered as: ${role} (total clients: ${clients.size})`)

    // Invia subito lo stato corrente, se la connessione e aperta.
    if (ws.readyState === 1) { // Stato WebSocket.OPEN
      try {
        ws.send(JSON.stringify({ type: 'state', state: gameState }))
      } catch (err) {
        console.error('Error sending initial state:', err)
      }
    }
  }

  /**
   * Gestisce un'azione di gioco dal controller
   */
  function handleAction(ws, msg) {
    // Solo i client controller possono inviare azioni.
    const client = clients.get(ws)
    if (!client || client.role !== 'controller') {
      sendError(ws, 'Only controllers can send actions')
      return
    }

    // Verifica il formato dell'azione ricevuta.
    if (!msg.action || !msg.action.type) {
      sendError(ws, 'Invalid action format')
      return
    }

    // Il momento di inizio del time out lo decide il server (non il client),
    // cosi' gameState.js resta puro/deterministico e testabile.
    const action = msg.action.type === 'startTimeout'
      ? { ...msg.action, startedAt: Date.now() }
      : msg.action

    // Applica l'azione allo stato della partita.
    const previousState = gameState
    try {
      gameState = applyAction(gameState, action)
    } catch (err) {
      console.error('Error applying action:', err)
      sendError(ws, 'Failed to apply action')
      gameState = previousState
      return
    }

    // Qualsiasi azione che tocca il time out invalida il timer di auto-chiusura
    // pendente; se il time out e' (ri)partito, ne arma uno nuovo.
    if (action.type === 'startTimeout' || action.type === 'stopTimeout') {
      annullaTimerTimeout()
    }
    if (action.type === 'startTimeout' && gameState.timeout) {
      timeoutTimer = setTimeout(() => {
        timeoutTimer = null
        gameState = applyAction(gameState, { type: 'stopTimeout' })
        broadcastState()
        options.onStateChange?.(gameState)
      }, DURATA_TIMEOUT_MS)
    }

    // Propaga il nuovo stato a tutti i client connessi.
    broadcastState()
    options.onStateChange?.(gameState)
  }

  /**
   * Gestisce una richiesta di sintesi vocale proveniente dal controller.
   * Il messaggio viene inoltrato solo ai client registrati come display.
   */
  function handleSpeak(ws, msg) {
    const client = clients.get(ws)
    if (!client || client.role !== 'controller') {
      sendError(ws, 'Only controllers can request speech')
      return
    }

    if (typeof msg.text !== 'string' || msg.text.trim() === '') {
      sendError(ws, 'Invalid speak payload')
      return
    }

    const speakMsg = JSON.stringify({ type: 'speak', text: msg.text.trim() })
    clients.forEach((meta, clientWs) => {
      if (meta.role === 'display' && clientWs.readyState === 1) {
        clientWs.send(speakMsg)
      }
    })
  }

  /**
   * Invia un messaggio di errore al client
   */
  function sendError(ws, message) {
    if (ws.readyState === 1) { // Stato WebSocket.OPEN
      try {
        ws.send(JSON.stringify({ type: 'error', message }))
      } catch (err) {
        console.error('Failed to send error message:', err)
      }
    }
  }

  /**
   * Invia lo stato corrente a tutti i client connessi.
   */
  function broadcastState() {
    const stateMsg = JSON.stringify({ type: 'state', state: gameState })
    wss.clients.forEach((client) => {
      if (client.readyState === 1) { // Stato WebSocket.OPEN
        client.send(stateMsg)
      }
    })
  }

  /**
   * Gestisce la chiusura della connessione
   */
  function handleClose(ws) {
    const client = clients.get(ws)
    const role = client?.role || 'unknown'
    console.log(`[WebSocket] Client disconnected (role: ${role})`)
    clients.delete(ws)
  }

  /**
   * Gestisce gli errori WebSocket
   */
  function handleError(err, ws) {
    console.error('WebSocket error:', err)

    // In caso di frame non validi, chiude forzatamente la connessione.
    if (err.code === 'WS_ERR_INVALID_CLOSE_CODE' || err.code === 'WS_ERR_INVALID_UTF8') {
      try {
        if (ws && ws.readyState === 1) { // Stato WebSocket.OPEN
          ws.terminate() // Chiusura forzata senza handshake di chiusura.
        }
      } catch (closeErr) {
        console.error('Error closing connection:', closeErr)
      }
    }
  }

  // Registra gli handler per ogni nuova connessione.
  wss.on('connection', (ws) => {
    // Imposta il tipo binario per ridurre i problemi di codifica.
    ws.binaryType = 'arraybuffer'

    ws.on('message', (data) => handleMessage(ws, data))
    ws.on('close', () => handleClose(ws))
    ws.on('error', (err) => handleError(err, ws))
  })

  // Espone un'API pubblica per controllo esterno, se necessario.
  return {
    getState: () => gameState,
    setState: (newState) => { gameState = newState },
    broadcastState,
    getClients: () => clients,
  }
}
