/**
 * Instagram N8n Publisher Service
 *
 * Publishes content to Instagram via an n8n webhook using the Instagram
 * Graph API (Content Publishing API).
 *
 * Flow:
 *   Mengo → n8n webhook → n8n publishes to Instagram → n8n calls back → Mengo updates status
 *
 * The publisher:
 *   1. Loads the InstagramN8nConfig for the publication's company
 *   2. Decrypts method-specific credentials
 *   3. Builds a payload with `publishMethod: 'graph_api'` and IG-specific data
 *   4. POSTs it to the configured n8n webhook URL
 *   5. Sets publication status to 'processing' (awaiting callback)
 *   6. On error, uses failAttempt() with retry logic (same as other publishers)
 *
 * The callback endpoint (instagramN8nCallback.ts) handles the result from n8n.
 */

import type { ISocialMediaPublication } from '../../models/SocialMediaPublication';
import { getInstagramN8nCredentials } from './instagramN8nConfigService';

const MAX_ATTEMPTS = 3;

// ============================================
// HELPERS
// ============================================

function recordError(publication: ISocialMediaPublication, code: string, message: string): void {
  const error = { code, message: message.slice(0, 500), at: new Date() };
  publication.lastError = error as any;
  publication.errorHistory.push(error as any);
}

async function failAttempt(publication: ISocialMediaPublication, code: string, message: string, retryable: boolean): Promise<void> {
  recordError(publication, code, message);
  publication.attemptCount += 1;
  publication.workerLockedAt = null as any;

  if (retryable && publication.attemptCount < MAX_ATTEMPTS) {
    publication.status = 'queued';
    publication.nextAttemptAt = new Date(Date.now() + Math.pow(4, publication.attemptCount) * 30 * 1000);
  } else {
    publication.status = 'failed';
    publication.nextAttemptAt = null as any;
  }
  await publication.save();
}

/** Classify an HTTP error from the n8n webhook call */
function classifyWebhookError(status: number, message: string): { code: string; retryable: boolean } {
  if (status === 404) {
    return { code: 'n8n_webhook_not_found', retryable: false };
  }
  if (status === 401 || status === 403) {
    return { code: 'n8n_auth_required', retryable: false };
  }
  if (status === 429) {
    return { code: 'rate_limited', retryable: true };
  }
  if (status >= 400 && status < 500) {
    return { code: 'n8n_client_error', retryable: false };
  }
  if (status >= 500) {
    return { code: 'n8n_server_error', retryable: true };
  }
  return { code: 'n8n_webhook_error', retryable: true };
}

// ============================================
// CREDENTIALS TYPE
// ============================================

type InstagramN8nCredentials = NonNullable<Awaited<ReturnType<typeof getInstagramN8nCredentials>>>;

// ============================================
// EXECUTE PUBLICATION
// ============================================

export async function executePublication(publication: ISocialMediaPublication): Promise<void> {
  const { getModels } = await import('../../models');
  const { SocialMediaPublication } = getModels();

  // Re-fetch to get the latest state
  const freshPublication = await SocialMediaPublication.findById(publication._id);
  if (!freshPublication) {
    console.error(`[InstagramN8nPublisher] Publication ${publication._id} not found`);
    return;
  }

  // Load configuration
  const credentials = await getInstagramN8nCredentials(freshPublication.companyId.toString());
  if (!credentials) {
    await failAttempt(freshPublication, 'not_configured', 'Instagram N8n channel is not configured or disconnected', false);
    return;
  }

  // Mark as uploading
  freshPublication.status = 'uploading';
  freshPublication.workerLockedAt = new Date();
  await freshPublication.save();

  try {
    // Build the payload to send to n8n
    const payload = buildPayload(freshPublication, credentials);

    // Send to n8n webhook
    const response = await fetch(credentials.webhookUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
      signal: AbortSignal.timeout(30_000), // 30 second timeout
    });

    if (!response.ok) {
      const body = await response.text().catch(() => '');
      const { code, retryable } = classifyWebhookError(response.status, body);
      let errorMsg = `n8n webhook returned HTTP ${response.status}`;
      if (response.status === 404) {
        errorMsg = `n8n webhook not found (HTTP 404). The workflow may be inactive or the webhook URL may be incorrect. Verify the workflow is active in n8n and the URL matches the Webhook trigger node path.`;
      } else if (response.status === 401 || response.status === 403) {
        errorMsg = `n8n webhook authentication failed (HTTP ${response.status}). The webhook may require authentication credentials.`;
      } else {
        errorMsg += `: ${body.slice(0, 200)}`;
      }
      await failAttempt(freshPublication, code, errorMsg, retryable);
      return;
    }

    // Success — n8n accepted the payload
    freshPublication.status = 'processing';
    freshPublication.workerLockedAt = null;

    // If n8n responded with a reference ID, store it
    try {
      const responseBody: any = await response.json();
      if (responseBody?.id || responseBody?.workflowId || responseBody?.executionId) {
        freshPublication.platformPostId = String(responseBody.id || responseBody.workflowId || responseBody.executionId);
      }
    } catch {
      // Response body may not be JSON — that's fine
    }

    await freshPublication.save();
    console.log(`[InstagramN8nPublisher] Publication ${freshPublication._id} sent to n8n, status=processing`);
  } catch (error: any) {
    // Network error (fetch threw)
    const isTimeout = error.name === 'TimeoutError' || error.name === 'AbortError';
    await failAttempt(
      freshPublication,
      isTimeout ? 'n8n_timeout' : 'n8n_network_error',
      isTimeout
        ? 'n8n webhook request timed out after 30 seconds'
        : `Failed to reach n8n webhook: ${error.message}`,
      true, // Network errors are always retryable
    );
  }
}

// ============================================
// BUILD PAYLOAD
// ============================================

function buildPayload(publication: ISocialMediaPublication, credentials: InstagramN8nCredentials): Record<string, any> {
  // Common payload structure
  const payload: Record<string, any> = {
    event: 'social.post.publish',
    publicationId: String(publication._id),
    companyId: publication.companyId.toString(),
    publishMethod: 'graph_api',
    callbackUrl: credentials.callbackUrl,
  };

  // Common publication data
  const data: Record<string, any> = {
    postType: publication.postType || credentials.postType || 'text',
    message: publication.description || publication.title || '',
  };

  // Add link if present
  if (publication.link) {
    data.link = publication.link;
  }

  // Add media file paths if present
  if (publication.mediaFilePaths && publication.mediaFilePaths.length > 0) {
    data.mediaFilePaths = publication.mediaFilePaths;
  }

  // Add scheduled publish time if present
  if (publication.publishAt) {
    data.scheduledPublishTime = new Date(publication.publishAt).toISOString();
  }

  // ── Graph API credentials ──
  data.igBusinessAccountId = credentials.igBusinessAccountId;
  data.igAccessToken = credentials.igAccessToken;
  if (credentials.appId) data.appId = credentials.appId;
  if (credentials.appSecret) data.appSecret = credentials.appSecret;

  payload.data = data;
  return payload;
}

// ============================================
// SYNC PUBLICATION STATUS
// ============================================

/**
 * For instagram-n8n publications, status is updated via the callback endpoint.
 * This function checks for stale 'processing' publications that haven't received
 * a callback within the timeout period (30 minutes) and marks them as failed.
 */
export async function syncPublicationStatus(publication: ISocialMediaPublication): Promise<void> {
  const { getModels } = await import('../../models');
  const { SocialMediaPublication } = getModels();

  const freshPublication = await SocialMediaPublication.findById(publication._id);
  if (!freshPublication) return;

  // Only process publications that are still in 'processing' status
  if (freshPublication.status !== 'processing') return;

  // Check if the publication has been in 'processing' for too long (30 minutes)
  const PROCESSING_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
  const updatedAt = freshPublication.updatedAt?.getTime() || freshPublication.createdAt?.getTime() || 0;
  const timeSinceUpdate = Date.now() - updatedAt;

  if (timeSinceUpdate > PROCESSING_TIMEOUT_MS) {
    await failAttempt(
      freshPublication,
      'callback_timeout',
      `n8n callback was not received within ${Math.round(PROCESSING_TIMEOUT_MS / 60000)} minutes. The n8n workflow may have failed or the callback URL may be incorrect.`,
      true, // Retryable — the workflow might just be slow
    );
    console.warn(`[InstagramN8nPublisher] Publication ${freshPublication._id} timed out waiting for callback`);
  }
}