/**
 * src/services/invoiceApprovalService.js
 *
 * Invoice approval Adaptive Card flow when invoice PDFs are posted to WO rooms.
 */

import { logger } from '../utils/logger.js';
import {
  getWorkOrderStatus,
  getInvoice,
  approveInvoice,
  rejectInvoice,
} from '../integrations/serviceChannel/client.js';
import webexService from './webexService.js';

const CARD_DEDUP_TTL_MS = 5 * 60 * 1000;
const _recentInvoiceCards = new Map();

function _pruneDedupCache(now = Date.now()) {
  for (const [k, ts] of _recentInvoiceCards.entries()) {
    if (now - ts > CARD_DEDUP_TTL_MS) _recentInvoiceCards.delete(k);
  }
}

function _formatMoney(n) {
  const v = Number(n);
  return Number.isFinite(v) ? `$${v.toFixed(2)}` : 'N/A';
}

function _invoiceStatusPrimary(invoice) {
  if (!invoice) return '';
  if (typeof invoice.Status === 'string') return invoice.Status;
  return invoice.Status?.Primary || invoice.Status?.Name || '';
}

function _isInvoiceApproved(invoice) {
  const status = _invoiceStatusPrimary(invoice).toLowerCase();
  return status === 'approved' || status.includes('approved');
}

function _isInvoiceRejected(invoice) {
  const status = _invoiceStatusPrimary(invoice).toLowerCase();
  return status === 'rejected' || status.includes('rejected');
}

function getPendingInvoiceCard(db, workOrderId) {
  return new Promise((resolve, reject) => {
    db.get(
      'SELECT workOrderId, roomId, messageId, invoiceId, postedAt FROM pending_invoice_approval_cards WHERE workOrderId = ?',
      [workOrderId],
      (err, row) => (err ? reject(err) : resolve(row || null))
    );
  });
}

function savePendingInvoiceCard(db, { workOrderId, roomId, messageId, invoiceId }) {
  return new Promise((resolve, reject) => {
    db.run(
      `INSERT OR REPLACE INTO pending_invoice_approval_cards (workOrderId, roomId, messageId, invoiceId, postedAt)
       VALUES (?, ?, ?, ?, ?)`,
      [workOrderId, roomId, messageId, invoiceId ?? null, new Date().toISOString()],
      (err) => (err ? reject(err) : resolve())
    );
  });
}

function clearPendingInvoiceCard(db, workOrderId) {
  return new Promise((resolve, reject) => {
    db.run(
      'DELETE FROM pending_invoice_approval_cards WHERE workOrderId = ?',
      [workOrderId],
      (err) => (err ? reject(err) : resolve())
    );
  });
}

async function deleteInvoiceCardMessage(messageId) {
  if (!messageId) return;
  try {
    const botClientMod = await import('../integrations/webex/botClient.js');
    await botClientMod.default.deleteMessage(messageId);
    logger('invoiceApproval', `Deleted invoice card message ${messageId}`);
  } catch (err) {
    logger('invoiceApproval', `Could not delete invoice card ${messageId}: ${err.message}`, 'warn');
  }
}

async function _removeInvoiceCard(bot, action) {
  const messageId = action?.messageId;
  if (!messageId) return;

  if (typeof bot?.censor === 'function') {
    try {
      await bot.censor(messageId);
      logger('invoiceApproval:submit', `Removed invoice card message ${messageId}`);
      return;
    } catch (err) {
      logger('invoiceApproval:submit', `bot.censor failed for ${messageId}: ${err.message}`, 'warn');
    }
  }

  await deleteInvoiceCardMessage(messageId);
}

export function buildInvoiceApprovalAdaptiveCard(woContext, invoiceContext = {}) {
  const woId = woContext?.workOrderId ?? woContext?.Id;
  const woNum = woContext?.woNumber ?? woContext?.WorkorderNumber ?? woId ?? '???';
  const woLink = `https://www.servicechannel.com/sc/wo/Workorders/index?id=${woId || woNum}`;
  const invoiceNumber = invoiceContext.invoiceNumber ?? '?';
  const invoiceTotal = invoiceContext.invoiceTotal;
  const approvalCode = String(invoiceContext.approvalCode ?? '').trim();
  const category = String(invoiceContext.category ?? '').trim();
  const invoiceId = invoiceContext.invoiceId;

  const body = [
    {
      type: 'TextBlock',
      text: 'Invoice Approval Required',
      size: 'medium',
      weight: 'bolder',
    },
    {
      type: 'TextBlock',
      text: `[WO-${woNum}](${woLink})`,
      isSubtle: true,
      wrap: true,
    },
    {
      type: 'FactSet',
      facts: [
        { title: 'Invoice #', value: String(invoiceNumber) },
        { title: 'Amount', value: _formatMoney(invoiceTotal) },
        { title: 'Approval Code', value: approvalCode || '(missing — approve in SC)' },
      ],
    },
  ];

  if (!approvalCode) {
    body.push({
      type: 'TextBlock',
      text: 'This work order has no Approval Code in ServiceChannel. Add one in SC before approving here.',
      color: 'attention',
      wrap: true,
      spacing: 'medium',
    });
  }

  body.push(
    {
      type: 'TextBlock',
      text: 'Category (for SC invoice approval)',
      weight: 'bolder',
      spacing: 'medium',
    },
    {
      type: 'Input.Text',
      id: 'category',
      value: category,
      placeholder: 'Category',
    },
    {
      type: 'TextBlock',
      text: 'Optional comment',
      spacing: 'small',
    },
    {
      type: 'Input.Text',
      id: 'comment',
      placeholder: 'Approval or rejection notes',
      isMultiline: true,
    }
  );

  const actions = [
    {
      type: 'Action.Submit',
      title: 'Approve Invoice',
      data: {
        action: 'approveInvoice',
        workOrderId: woId,
        invoiceId,
        approvalCode,
        category,
      },
    },
    {
      type: 'Action.Submit',
      title: 'Reject Invoice',
      data: {
        action: 'rejectInvoice',
        workOrderId: woId,
        invoiceId,
      },
    },
    {
      type: 'Action.Submit',
      title: 'Cancel',
      associatedInputs: 'none',
      data: {
        action: 'dismissInvoiceCard',
        workOrderId: woId,
        invoiceId,
      },
    },
  ];

  return {
    $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
    type: 'AdaptiveCard',
    version: '1.3',
    body,
    actions,
  };
}

export async function postInvoiceApprovalCard({ db, webex, roomId, workOrderId, woNumber = null }) {
  if (!db || !webex || !roomId || !workOrderId) return;

  const dedupKey = `wo:${workOrderId}:invoice`;
  const now = Date.now();
  _pruneDedupCache(now);
  const lastPostedAt = _recentInvoiceCards.get(dedupKey);
  if (lastPostedAt && (now - lastPostedAt) < CARD_DEDUP_TTL_MS) {
    logger(
      'invoiceApproval',
      `Skipping duplicate invoice card for WO ${workOrderId} (posted ${Math.round((now - lastPostedAt) / 1000)}s ago)`
    );
    return;
  }
  _recentInvoiceCards.set(dedupKey, now);

  try {
    const woStatus = await getWorkOrderStatus(workOrderId);
    if (!woStatus?.invoiceId) {
      logger('invoiceApproval', `No invoice on WO ${workOrderId} — skipping card`);
      _recentInvoiceCards.delete(dedupKey);
      return;
    }

    const invoiceDetails = await getInvoice(woStatus.invoiceId);
    if (invoiceDetails && (_isInvoiceApproved(invoiceDetails) || _isInvoiceRejected(invoiceDetails))) {
      logger(
        'invoiceApproval',
        `Invoice ${woStatus.invoiceId} already ${_invoiceStatusPrimary(invoiceDetails)} — skipping card`
      );
      _recentInvoiceCards.delete(dedupKey);
      return;
    }

    const invoiceContext = {
      invoiceId: woStatus.invoiceId,
      invoiceNumber: woStatus.invoiceNumber ?? invoiceDetails?.Number,
      invoiceTotal: woStatus.invoiceTotal ?? invoiceDetails?.InvoiceTotal ?? invoiceDetails?.Total,
      approvalCode: woStatus.approvalCode,
      category: woStatus.category,
    };

    const card = buildInvoiceApprovalAdaptiveCard(
      { workOrderId, woNumber: woNumber || woStatus.woNumber },
      invoiceContext
    );

    const sender = webex && typeof webex.sendAdaptiveCard === 'function' ? webex : webexService;
    const fallback =
      `Invoice approval for WO-${woNumber || woStatus.woNumber}. Invoice #${invoiceContext.invoiceNumber || woStatus.invoiceId}.`;

    const existing = await getPendingInvoiceCard(db, workOrderId);
    if (existing?.messageId) {
      await deleteInvoiceCardMessage(existing.messageId);
    }

    const cardMsg = await sender.sendAdaptiveCard(roomId, card, fallback);
    if (cardMsg?.id) {
      await savePendingInvoiceCard(db, {
        workOrderId,
        roomId,
        messageId: cardMsg.id,
        invoiceId: woStatus.invoiceId,
      });
    }

    logger(
      'invoiceApproval',
      `Posted invoice approval card for WO-${woNumber || woStatus.woNumber} in room ${roomId} (invoiceId=${woStatus.invoiceId}, messageId=${cardMsg?.id || 'n/a'})`
    );
  } catch (err) {
    _recentInvoiceCards.delete(dedupKey);
    logger('invoiceApproval', `Failed to post invoice card for WO ${workOrderId}: ${err.message}`, 'error');
    throw err;
  }
}

function _isInvoiceResolved(noteData) {
  const note = String(noteData || '');
  if (/Invoice\s*#?\s*\d+\s+has been approved/i.test(note)) return true;
  if (/Invoice\s*#?\s*\d+\s+has been rejected/i.test(note)) return true;
  if (/Invoice has been approved/i.test(note)) return true;
  if (/Invoice has been rejected/i.test(note)) return true;
  return false;
}

export async function removeInvoiceCardIfResolved({ db, workOrderId, noteData }) {
  if (!db || !workOrderId) return;
  if (!_isInvoiceResolved(noteData)) return;

  try {
    const row = await getPendingInvoiceCard(db, workOrderId);
    if (!row?.messageId) return;

    await deleteInvoiceCardMessage(row.messageId);
    await clearPendingInvoiceCard(db, workOrderId);
    logger('invoiceApproval', `Removed stale invoice card for WO ${workOrderId} (resolved in ServiceChannel)`);
  } catch (err) {
    logger('invoiceApproval', `Failed to remove resolved invoice card for WO ${workOrderId}: ${err.message}`, 'warn');
  }
}

export async function handleInvoiceApprovalSubmit(bot, trigger, { db = null } = {}) {
  const action = trigger?.attachmentAction;
  if (!action || !action.inputs) return;

  const inputs = action.inputs;
  const actionType = inputs.action;

  if (actionType === 'dismissInvoiceCard') {
    const woId = inputs.workOrderId;
    await _removeInvoiceCard(bot, action);
    if (db && woId) {
      try {
        await clearPendingInvoiceCard(db, woId);
      } catch (err) {
        logger('invoiceApproval:submit', `Could not clear pending invoice card for WO ${woId}: ${err.message}`, 'warn');
      }
    }
    try {
      await bot.say('Invoice approval card dismissed. Approve in ServiceChannel when ready.');
    } catch (_) {}
    return;
  }

  if (actionType !== 'approveInvoice' && actionType !== 'rejectInvoice') return;

  const woId = inputs.workOrderId;
  const invoiceId = inputs.invoiceId;
  const comment = String(inputs.comment || '').trim();
  const category = String(inputs.category ?? '').trim();

  if (!woId || !invoiceId) {
    try {
      await bot.say('Missing work order or invoice in submission.');
    } catch (_) {}
    return;
  }

  let approver = 'Webex user';
  let approverEmail = '';
  try {
    const botClientMod = await import('../integrations/webex/botClient.js');
    const details = await botClientMod.default.getPersonDetails(action.personId);
    approverEmail = details?.emails?.[0] || '';
    approver = details?.displayName || approverEmail || action.personId || 'Webex user';
  } catch (e) {
    logger('invoiceApproval:submit', `Could not resolve person ${action.personId}: ${e.message}`, 'warn');
  }

  if (actionType === 'approveInvoice') {
    const approvalCode = String(inputs.approvalCode ?? '').trim();
    if (!approvalCode) {
      try {
        await bot.say('Cannot approve: this work order has no Approval Code in ServiceChannel. Add one in SC or approve manually.');
      } catch (_) {}
      return;
    }

    try {
      await approveInvoice(invoiceId, {
        approvalCode,
        comments: comment || `Approved by ${approver} via ServChan`,
        category,
      });

      await _removeInvoiceCard(bot, action);
      if (db) {
        await clearPendingInvoiceCard(db, woId);
      }

      await bot.say({
        markdown:
          `✅ **Invoice approved** by **${approver}** for WO **${woId}**.` +
          (comment ? `\n\nComment: ${comment}` : ''),
      });
      logger('invoiceApproval:submit', `Approved invoice ${invoiceId} for WO ${woId} by ${approver}`);
    } catch (err) {
      const msg = err.message || String(err);
      const isTokenError = msg.includes('token fetch failed');
      const isMli = msg.includes('503') || msg.toLowerCase().includes('mli');
      const reply = isTokenError
        ? `❌ **ServiceChannel login failed** — fix SC credentials and restart ServChan, then retry.`
        : isMli
          ? `❌ Invoice approval via API is unavailable (MLI enabled). Approve manually in ServiceChannel.\n\nDetails: ${msg}`
          : `❌ Failed to approve invoice: ${msg}. Approve manually in ServiceChannel.`;
      await bot.say({ markdown: reply });
      logger('invoiceApproval:submit', `Approve failed for invoice ${invoiceId}: ${msg}`, 'error');
    }
    return;
  }

  // rejectInvoice
  try {
    await rejectInvoice(invoiceId, {
      comments: comment || `Rejected by ${approver} via ServChan`,
      isNotifyProvider: true,
    });

    await _removeInvoiceCard(bot, action);
    if (db) {
      await clearPendingInvoiceCard(db, woId);
    }

    await bot.say({
      markdown:
        `Invoice **rejected** by **${approver}** for WO **${woId}**.` +
        (comment ? `\n\nComment: ${comment}` : ''),
    });
    logger('invoiceApproval:submit', `Rejected invoice ${invoiceId} for WO ${woId} by ${approver}`);
  } catch (err) {
    const msg = err.message || String(err);
    await bot.say({ markdown: `❌ Failed to reject invoice: ${msg}. Reject manually in ServiceChannel.` });
    logger('invoiceApproval:submit', `Reject failed for invoice ${invoiceId}: ${msg}`, 'error');
  }
}

export default {
  buildInvoiceApprovalAdaptiveCard,
  postInvoiceApprovalCard,
  removeInvoiceCardIfResolved,
  handleInvoiceApprovalSubmit,
};
