/**
 * src/services/staleWorkOrderReportService.js
 *
 * Generates the stale work order report for the /stale-workorders endpoint.
 *
 * Phase 0 logic:
 * - Uses only work orders present in our local mappings table.
 * - Determines staleness using:
 *     - Work order UpdatedDate
 *     - Most recent note's DateCreated
 *     - ScheduledDate (with special rules)
 * - Flags work orders with no meaningful update in the last 2 days
 *   (or 7 days if they have a future scheduled date).
 */

import pLimit from 'p-limit';
import { fetchWithRetry, sleep } from '../integrations/serviceChannel/client.js';
import { loadSecrets } from '../config/secrets.js';
import { logger } from '../utils/logger.js';

const secrets = loadSecrets();

// Very conservative concurrency + spacing for ServiceChannel bulk scans.
// Even 2 in parallel can trigger 429s without delays + retries.
const limit = pLimit(2);

/**
 * Fetch full details + notes for a single work order.
 * Uses fetchWithRetry (handles 429/5xx with backoff + Retry-After).
 * Calls are sequential (not parallel) to keep instantaneous load low.
 */
async function fetchWorkOrderDetails(workOrderId) {
  // Work order with the fields we need for staleness + display
  const woRes = await fetchWithRetry(
    `/workorders/${workOrderId}`,
    {
      params: {
        $select: 'Id,WorkorderNumber,Status,UpdatedDate,Description,Trade,ProviderName,LocationName,LocationStoreId,ScheduledDate'
      },
      timeout: 30000,
    }
  );

  // Notes (most recent by DateCreated is used for last activity)
  const notesRes = await fetchWithRetry(
    `/workorders/${workOrderId}/notes`,
    { timeout: 30000 }
  );

  const workOrder = woRes.data;
  const notes = notesRes.data?.Notes || [];

  // Find most recent note by DateCreated
  let lastNote = null;
  if (notes.length > 0) {
    lastNote = notes.reduce((latest, note) => {
      const noteDate = new Date(note.DateCreated);
      return !latest || noteDate > new Date(latest.DateCreated) ? note : latest;
    });
  }

  return {
    workOrder,
    notes,
    lastNote,
  };
}

/**
 * Determine if a work order is stale and why.
 */
function evaluateStaleness(workOrder, lastNote) {
  const now = new Date();

  const updatedDate = workOrder.UpdatedDate ? new Date(workOrder.UpdatedDate) : null;
  const scheduledDate = workOrder.ScheduledDate ? new Date(workOrder.ScheduledDate) : null;
  const lastNoteDate = lastNote?.DateCreated ? new Date(lastNote.DateCreated) : null;

  // Last meaningful activity = most recent of UpdatedDate and last note
  const activityDates = [updatedDate, lastNoteDate].filter(Boolean);
  const lastActivity = activityDates.length > 0
    ? new Date(Math.max(...activityDates.map(d => d.getTime())))
    : null;

  if (!lastActivity) {
    // No activity data at all — treat as very stale
    return {
      isStale: true,
      daysSinceActivity: 999,
      reason: 'No activity data available',
      category: 'no-data',
    };
  }

  const daysSinceActivity = Math.floor((now - lastActivity) / (1000 * 60 * 60 * 24));

  // Rule per user requirements:
  // - If has ScheduledDate:
  //     - Flag if ScheduledDate is in the past, OR
  //     - Flag if no update in the last 7 days (even with future schedule)
  // - Otherwise:
  //     - Flag if no update in last 2 days

  if (scheduledDate) {
    const scheduledInPast = scheduledDate <= now;

    if (scheduledInPast) {
      return {
        isStale: true,
        daysSinceActivity,
        reason: `Scheduled for ${scheduledDate.toISOString().split('T')[0]} (date has passed)`,
        category: 'scheduled-past',
      };
    }

    if (daysSinceActivity >= 7) {
      return {
        isStale: true,
        daysSinceActivity,
        reason: `No update in ${daysSinceActivity} days (scheduled for future: ${scheduledDate.toISOString().split('T')[0]})`,
        category: 'long-inactive-with-schedule',
      };
    }

    return { isStale: false, daysSinceActivity, reason: null, category: null };
  }

  // No scheduled date
  if (daysSinceActivity >= 2) {
    return {
      isStale: true,
      daysSinceActivity,
      reason: `No update in ${daysSinceActivity} days`,
      category: 'inactive',
    };
  }

  return { isStale: false, daysSinceActivity, reason: null, category: null };
}

/**
 * Main function to generate the stale work orders report.
 */
export async function getStaleWorkOrdersReport(db) {
  if (!secrets.serviceChannel.clientId) {
    return {
      error: true,
      message: 'ServiceChannel credentials are not configured in the environment (SC_CLIENT_ID, SC_CLIENT_SECRET, SC_USERNAME, SC_PASSWORD).',
      summary: { totalMonitored: 0, staleCount: 0, generatedAt: new Date().toISOString() },
      items: [],
    };
  }

  // 1. Get all work orders we care about from our local DB
  // Note: We only select columns that are guaranteed to exist in the production schema.
  // storeNumber was added later and may not exist in the live production database.
  const mappings = await new Promise((resolve, reject) => {
    db.all('SELECT workOrderId, roomId FROM mappings', [], (err, rows) => {
      if (err) reject(err);
      else resolve(rows || []);
    });
  });

  if (mappings.length === 0) {
    return {
      generatedAt: new Date().toISOString(),
      totalMonitored: 0,
      staleCount: 0,
      items: [],
    };
  }

  // 2. Fetch details for all work orders (very low concurrency + spacing + retries)
  const fetchPromises = mappings.map((mapping, index) =>
    limit(async () => {
      try {
        // Spread start times even under the limiter to avoid thundering herd
        if (index > 0) {
          await sleep(320 + Math.random() * 220); // ~320-540ms stagger
        }

        const details = await fetchWorkOrderDetails(mapping.workOrderId);
        const evaluation = evaluateStaleness(details.workOrder, details.lastNote);

        return {
          ...mapping,
          ...details,
          ...evaluation,
        };
      } catch (err) {
        // After all retries inside fetchWithRetry, this is a hard failure
        logger('staleWorkOrderReport', `Failed to fetch WO ${mapping.workOrderId}: ${err.message}`, 'warn');
        return {
          ...mapping,
          error: err.message,
          isStale: false,
        };
      }
    })
  );

  const results = await Promise.all(fetchPromises);

  // 3. Filter to stale + actionable items and sort by days since activity (desc)
  // - Exclude Completed items (Primary status === 'COMPLETED')
  // - Errors here are items that failed even after retries + backoff
  const staleItems = results
    .filter(r => r.isStale && !r.error && (r.workOrder?.Status?.Primary?.toUpperCase() !== 'COMPLETED'))
    .sort((a, b) => b.daysSinceActivity - a.daysSinceActivity);

  const summary = {
    totalMonitored: mappings.length,
    staleCount: staleItems.length,
    generatedAt: new Date().toISOString(),
  };

  return {
    summary,
    items: staleItems,
  };
}