import { getDb } from '../db/client.js'
import { getMaxCustomersPerMitra } from './config.service.js'
import { sendToUser } from '../plugins/websocket.js'
import { sendPushNotification } from './notification.service.js'
import { startSessionTimer } from './session-timer.service.js'
import { startSessionListener } from './chat-handler.service.js'
import { UserType, SessionStatus, NotificationResponse, TransactionType, WsMessage, TopicSensitivity } from '../constants.js'

const sql = getDb()

// Timeout map for active pairing requests (sessionId → timeoutId)
const pairingTimeouts = new Map()

// Send notification to mitra via WebSocket, fall back to FCM if offline
const notifyMitra = async (mitraId, data) => {
  const sent = sendToUser(UserType.MITRA, mitraId, data)
  if (!sent) {
    // Mitra not connected via WebSocket — send FCM push
    if (data.type === WsMessage.CHAT_REQUEST) {
      await sendPushNotification(UserType.MITRA, mitraId, {
        title: 'Permintaan Chat Baru',
        body: 'Ada pelanggan yang ingin curhat! Ketuk untuk menerima.',
        data: { type: WsMessage.CHAT_REQUEST, session_id: data.session_id, action: 'open_accept' },
      })
    }
  }
}

// Send notification to customer via WebSocket, fall back to FCM if offline
const notifyCustomer = async (customerId, data) => {
  const sent = sendToUser(UserType.CUSTOMER, customerId, data)
  console.log(`[notifyCustomer] customerId=${customerId} type=${data.type} ws_sent=${sent}`)
  if (!sent) {
    if (data.type === WsMessage.PAIRED) {
      await sendPushNotification(UserType.CUSTOMER, customerId, {
        title: 'Bestie Ditemukan!',
        body: `${data.mitra_display_name} siap menemanimu curhat`,
        data: { type: WsMessage.PAIRED, session_id: data.session_id },
      })
    } else if (data.type === WsMessage.SESSION_EXPIRED) {
      await sendPushNotification(UserType.CUSTOMER, customerId, {
        title: 'Tidak Ada Bestie',
        body: 'Maaf, tidak ada bestie yang tersedia saat ini.',
        data: { type: WsMessage.SESSION_EXPIRED, session_id: data.session_id },
      })
    }
  }
}

export const findAvailableMitras = async () => {
  const { max_customers_per_mitra } = await getMaxCustomersPerMitra()
  const mitras = await sql`
    SELECT m.id, m.display_name
    FROM mitras m
    INNER JOIN mitra_online_status s ON s.mitra_id = m.id
    WHERE m.is_active = true
      AND s.is_online = true
      AND (
        SELECT COUNT(*) FROM chat_sessions cs
        WHERE cs.mitra_id = m.id AND cs.status IN (${SessionStatus.ACTIVE}, ${SessionStatus.PENDING_PAYMENT})
      ) < ${max_customers_per_mitra}
  `
  return mitras
}

export const createPairingRequest = async (customerId, { duration_minutes, price, is_free_trial, topic_sensitivity } = {}) => {
  // Check for existing active session or request
  const [existing] = await sql`
    SELECT id, status FROM chat_sessions
    WHERE customer_id = ${customerId}
      AND status IN (${SessionStatus.SEARCHING}, ${SessionStatus.PENDING_ACCEPTANCE}, ${SessionStatus.PENDING_PAYMENT}, ${SessionStatus.ACTIVE})
  `
  if (existing) {
    throw Object.assign(new Error('Customer already has an active session or request'), {
      code: 'ALREADY_ACTIVE', statusCode: 409,
    })
  }

  const availableMitras = await findAvailableMitras()
  if (availableMitras.length === 0) {
    throw Object.assign(new Error('No bestie available'), {
      code: 'NO_MITRA_AVAILABLE', statusCode: 404,
    })
  }

  const resolvedTopic = topic_sensitivity === TopicSensitivity.SENSITIVE
    ? TopicSensitivity.SENSITIVE
    : TopicSensitivity.REGULAR

  // Create session with duration/price/topic
  const [session] = await sql`
    INSERT INTO chat_sessions (customer_id, status, duration_minutes, price, is_free_trial, topic_sensitivity)
    VALUES (${customerId}, ${SessionStatus.PENDING_ACCEPTANCE}, ${duration_minutes || null}, ${price || 0}, ${is_free_trial || false}, ${resolvedTopic})
    RETURNING id, customer_id, status, duration_minutes, price, is_free_trial, topic_sensitivity, created_at
  `

  // Create notifications for all available mitras
  for (const mitra of availableMitras) {
    const [{ count: activeCount }] = await sql`
      SELECT COUNT(*)::int AS count FROM chat_sessions
      WHERE mitra_id = ${mitra.id}
        AND status IN (${SessionStatus.ACTIVE}, ${SessionStatus.PENDING_PAYMENT})
    `
    await sql`
      INSERT INTO chat_request_notifications (session_id, mitra_id, active_session_count)
      VALUES (${session.id}, ${mitra.id}, ${activeCount})
    `
    // Notify mitra via WebSocket (FCM fallback if offline)
    await notifyMitra(mitra.id, {
      type: WsMessage.CHAT_REQUEST,
      session_id: session.id,
      created_at: session.created_at,
      duration_minutes: session.duration_minutes,
      is_free_trial: session.is_free_trial,
      topic_sensitivity: session.topic_sensitivity,
    })
  }

  // Start 60s timeout
  const timeoutId = setTimeout(async () => {
    try {
      await expirePairingRequest(session.id)
    } catch (_) {}
  }, 60_000)
  pairingTimeouts.set(session.id, timeoutId)

  return session
}

export const acceptPairingRequest = async (sessionId, mitraId) => {
  // Use a transaction-like approach: update only if status is still pending_acceptance
  const [session] = await sql`
    UPDATE chat_sessions
    SET mitra_id = ${mitraId}, status = ${SessionStatus.PENDING_PAYMENT}, paired_at = NOW()
    WHERE id = ${sessionId} AND status = ${SessionStatus.PENDING_ACCEPTANCE} AND mitra_id IS NULL
    RETURNING id, customer_id, mitra_id, status, paired_at
  `

  if (!session) {
    throw Object.assign(new Error('Request already accepted or expired'), {
      code: 'REQUEST_UNAVAILABLE', statusCode: 409,
    })
  }

  // Mark this mitra's notification as accepted
  await sql`
    UPDATE chat_request_notifications
    SET response = ${NotificationResponse.ACCEPTED}, responded_at = NOW()
    WHERE session_id = ${sessionId} AND mitra_id = ${mitraId}
  `

  // Mark other mitras' notifications as missed (another mitra accepted)
  await sql`
    UPDATE chat_request_notifications
    SET response = ${NotificationResponse.MISSED}, responded_at = NOW()
    WHERE session_id = ${sessionId} AND mitra_id != ${mitraId} AND response IS NULL
  `

  // Clear timeout
  const timeoutId = pairingTimeouts.get(sessionId)
  if (timeoutId) {
    clearTimeout(timeoutId)
    pairingTimeouts.delete(sessionId)
  }

  // Auto-skip payment for now: move to active and set expires_at
  const [activeSession] = await sql`
    UPDATE chat_sessions
    SET status = ${SessionStatus.ACTIVE},
        expires_at = CASE
          WHEN duration_minutes IS NOT NULL THEN NOW() + (duration_minutes || ' minutes')::interval
          ELSE NULL
        END
    WHERE id = ${sessionId}
    RETURNING id, customer_id, mitra_id, status, paired_at, duration_minutes, price, is_free_trial, expires_at
  `

  // Record transaction
  if (activeSession.duration_minutes) {
    const txType = activeSession.is_free_trial ? TransactionType.FREE_TRIAL : TransactionType.PAID
    await sql`
      INSERT INTO customer_transactions (customer_id, session_id, type, amount)
      VALUES (${activeSession.customer_id}, ${sessionId}, ${txType}, ${activeSession.price || 0})
    `
  }

  // Start session timer if duration is set
  if (activeSession.expires_at) {
    startSessionTimer(sessionId, activeSession.expires_at)
  }

  // Start chat message listener for this session
  startSessionListener(sessionId)

  // Get mitra display name for customer notification
  const [mitra] = await sql`
    SELECT display_name FROM mitras WHERE id = ${mitraId}
  `

  // Notify customer via WebSocket (FCM fallback)
  await notifyCustomer(activeSession.customer_id, {
    type: WsMessage.PAIRED,
    session_id: sessionId,
    mitra_display_name: mitra.display_name,
    status: SessionStatus.ACTIVE,
  })

  // Notify other mitras to dismiss the request
  const notifications = await sql`
    SELECT mitra_id FROM chat_request_notifications
    WHERE session_id = ${sessionId} AND mitra_id != ${mitraId}
  `
  for (const n of notifications) {
    await notifyMitra(n.mitra_id, {
      type: WsMessage.CHAT_REQUEST_CLOSED,
      session_id: sessionId,
      reason: 'accepted_by_other',
    })
  }

  return activeSession
}

export const declinePairingRequest = async (sessionId, mitraId) => {
  await sql`
    UPDATE chat_request_notifications
    SET response = ${NotificationResponse.DECLINED}, responded_at = NOW()
    WHERE session_id = ${sessionId} AND mitra_id = ${mitraId} AND response IS NULL
  `
}

export const cancelPairingRequest = async (sessionId, customerId) => {
  const [session] = await sql`
    UPDATE chat_sessions
    SET status = ${SessionStatus.CANCELLED}
    WHERE id = ${sessionId} AND customer_id = ${customerId}
      AND status IN (${SessionStatus.SEARCHING}, ${SessionStatus.PENDING_ACCEPTANCE})
    RETURNING id, status
  `

  if (!session) {
    throw Object.assign(new Error('Cannot cancel this request'), {
      code: 'CANNOT_CANCEL', statusCode: 409,
    })
  }

  // Clear timeout
  const timeoutId = pairingTimeouts.get(sessionId)
  if (timeoutId) {
    clearTimeout(timeoutId)
    pairingTimeouts.delete(sessionId)
  }

  // Mark all notifications as ignored
  await sql`
    UPDATE chat_request_notifications
    SET response = ${NotificationResponse.IGNORED}, responded_at = NOW()
    WHERE session_id = ${sessionId} AND response IS NULL
  `

  // Notify mitras to dismiss (customer cancelled)
  const notifications = await sql`
    SELECT mitra_id FROM chat_request_notifications WHERE session_id = ${sessionId}
  `
  for (const n of notifications) {
    await notifyMitra(n.mitra_id, {
      type: WsMessage.CHAT_REQUEST_CLOSED,
      session_id: sessionId,
      reason: 'cancelled_by_customer',
    })
  }

  return session
}

export const expirePairingRequest = async (sessionId) => {
  const [session] = await sql`
    UPDATE chat_sessions
    SET status = ${SessionStatus.EXPIRED}
    WHERE id = ${sessionId} AND status = ${SessionStatus.PENDING_ACCEPTANCE}
    RETURNING id, customer_id, status
  `
  if (!session) return null

  pairingTimeouts.delete(sessionId)

  // Mark all pending notifications as ignored
  await sql`
    UPDATE chat_request_notifications
    SET response = ${NotificationResponse.IGNORED}, responded_at = NOW()
    WHERE session_id = ${sessionId} AND response IS NULL
  `

  // Notify customer via WebSocket (FCM fallback)
  await notifyCustomer(session.customer_id, {
    type: WsMessage.SESSION_EXPIRED,
    session_id: sessionId,
  })

  // Notify mitras to dismiss (request expired)
  const notifications = await sql`
    SELECT mitra_id FROM chat_request_notifications WHERE session_id = ${sessionId}
  `
  for (const n of notifications) {
    await notifyMitra(n.mitra_id, {
      type: WsMessage.CHAT_REQUEST_CLOSED,
      session_id: sessionId,
      reason: 'expired',
    })
  }

  return session
}

export const getPendingRequestsForMitra = async (mitraId) => {
  const rows = await sql`
    SELECT cs.id AS session_id, cs.duration_minutes, cs.is_free_trial, cs.topic_sensitivity, cs.created_at
    FROM chat_request_notifications crn
    JOIN chat_sessions cs ON cs.id = crn.session_id
    WHERE crn.mitra_id = ${mitraId}
      AND crn.response IS NULL
      AND cs.status = ${SessionStatus.PENDING_ACCEPTANCE}
    ORDER BY cs.created_at ASC
  `
  return rows
}

export const getSessionStatus = async (sessionId) => {
  const [session] = await sql`
    SELECT cs.id, cs.customer_id, cs.mitra_id, cs.status, cs.topic_sensitivity,
      cs.created_at, cs.paired_at, cs.ended_at, cs.ended_by,
      m.display_name AS mitra_display_name
    FROM chat_sessions cs
    LEFT JOIN mitras m ON m.id = cs.mitra_id
    WHERE cs.id = ${sessionId}
  `
  return session
}
