/**
 * Autotask Webhook Manager
 * Registers, manages, and deregisters webhooks with the Autotask REST API.
 * Autotask webhooks must be created via API — there is no GUI-based creation.
 *
 * Supported entities: Companies, Contacts, ConfigurationItems, Tickets, TicketNotes
 */

import { AutotaskClient } from './autotask-client';
import { postgresClient } from './postgres-client';
import {
  WebhookEntityType,
  WEBHOOK_SUPPORTED_ENTITIES,
  AutotaskWebhookRegistration,
  AutotaskWebhookRegistrationResult,
} from '../types/webhook';

/**
 * Maps our WebhookEntityType enum to the Autotask REST API webhook entity name.
 * e.g. "Tickets" → "TicketWebhooks"
 */
function getWebhookEntityName(entityType: WebhookEntityType): string {
  const map: Record<string, string> = {
    Companies: 'CompanyWebhooks',
    Contacts: 'ContactWebhooks',
    ConfigurationItems: 'ConfigurationItemWebhooks',
    Tickets: 'TicketWebhooks',
    TicketNotes: 'TicketNoteWebhooks',
    TimeEntries: 'TimeEntryWebhooks',
    Tasks: 'TaskWebhooks',
    Projects: 'ProjectWebhooks',
    Contracts: 'ContractWebhooks',
  };
  return map[entityType] || `${entityType}Webhooks`;
}

export class AutotaskWebhookManager {
  private client: AutotaskClient;
  private baseUrl: string;
  private webhookBaseUrl: string;
  private secretKey: string;
  private notificationEmail: string;

  constructor() {
    this.client = new AutotaskClient({
      apiUrl: process.env.AUTOTASK_API_URL || '',
      username: process.env.AUTOTASK_USERNAME || '',
      password: process.env.AUTOTASK_SECRET || '',
      apiIntegrationCode: process.env.AUTOTASK_API_INTEGRATION_CODE || '',
    });
    this.baseUrl = process.env.AUTOTASK_API_URL || '';
    this.webhookBaseUrl = process.env.WEBHOOK_BASE_URL || '';
    this.secretKey = process.env.AUTOTASK_WEBHOOK_SECRET || '';
    this.notificationEmail = process.env.AUTOTASK_USERNAME?.replace('@', '+webhooks@') || '';
  }

  /**
   * Register webhooks for all supported entities
   */
  async registerAll(): Promise<AutotaskWebhookRegistrationResult[]> {
    const results: AutotaskWebhookRegistrationResult[] = [];

    for (const entityType of WEBHOOK_SUPPORTED_ENTITIES) {
      try {
        const result = await this.registerWebhook(entityType);
        results.push(result);
      } catch (error) {
        const msg = error instanceof Error ? error.message : String(error);
        console.error(`[WEBHOOK-MGR] Failed to register ${entityType}:`, msg);
        results.push({
          entityType,
          webhookId: 0,
          fieldsRegistered: 0,
          excludedResources: 0,
          success: false,
          error: msg,
        });
      }
    }

    return results;
  }

  /**
   * Register a webhook for a single entity type
   */
  async registerWebhook(entityType: WebhookEntityType): Promise<AutotaskWebhookRegistrationResult> {
    const webhookEntityName = getWebhookEntityName(entityType);
    console.log(`[WEBHOOK-MGR] Registering webhook for ${entityType} via ${webhookEntityName}`);

    // 1. Check if we already have a webhook registered
    const existing = await this.getExistingWebhookId(entityType);
    if (existing) {
      console.log(`[WEBHOOK-MGR] Webhook already registered for ${entityType} (ID: ${existing}), deleting first`);
      await this.deleteWebhook(entityType, existing);
    }

    // 2. Create the webhook
    const webhookUrl = `${this.webhookBaseUrl}/api/webhooks/autotask`;
    const deactivationUrl = `${this.webhookBaseUrl}/api/webhooks/autotask?deactivated=true`;

    const registration: AutotaskWebhookRegistration = {
      IsActive: true,
      DeactivationUrl: deactivationUrl,
      IsSubscribedToCreateEvents: true,
      IsSubscribedToUpdateEvents: true,
      IsSubscribedToDeleteEvents: true,
      Name: `Pulse - ${entityType}`,
      SecretKey: this.secretKey,
      SendThresholdExceededNotification: true,
      WebhookUrl: webhookUrl,
      NotificationEmailAddress: this.notificationEmail,
    };

    const createUrl = `${this.baseUrl}/${webhookEntityName}`;
    const createResponse = await this.apiCall<{ itemId: number }>(createUrl, 'POST', registration);
    const webhookId = createResponse.itemId;

    console.log(`[WEBHOOK-MGR] Created webhook for ${entityType}, ID: ${webhookId}`);

    // 3. Discover and add trigger fields
    let fieldsRegistered = 0;
    try {
      fieldsRegistered = await this.addTriggerFields(webhookEntityName, webhookId);
    } catch (error) {
      console.warn(`[WEBHOOK-MGR] Could not add trigger fields for ${entityType}:`, error);
    }

    // 4. Exclude the API user resource to prevent infinite loops
    let excludedResources = 0;
    try {
      excludedResources = await this.excludeApiResource(webhookEntityName, webhookId);
    } catch (error) {
      console.warn(`[WEBHOOK-MGR] Could not exclude API resource for ${entityType}:`, error);
    }

    // 5. Save webhook ID to database
    await this.saveWebhookConfig(entityType, webhookId);

    return {
      entityType,
      webhookId,
      fieldsRegistered,
      excludedResources,
      success: true,
    };
  }

  /**
   * Discover available webhook fields and register them as triggers.
   * The fields endpoint is {Entity}WebhookFields/entityInformation/fields
   * (a separate entity, not a sub-path of the webhook).
   */
  private async addTriggerFields(webhookEntityName: string, webhookId: number): Promise<number> {
    // Derive the fields entity name: "TicketWebhooks" → "TicketWebhookFields"
    const fieldsEntityName = webhookEntityName.replace('Webhooks', 'WebhookFields');
    const fieldsUrl = `${this.baseUrl}/${fieldsEntityName}/entityInformation/fields`;
    const fieldsResponse = await this.apiCall<{ fields: any[] }>(fieldsUrl, 'GET');

    // Find the fieldID picklist to discover available trigger fields
    const fieldIdField = fieldsResponse.fields?.find((f: any) => f.name === 'fieldID');
    if (!fieldIdField?.picklistValues) {
      console.warn(`[WEBHOOK-MGR] No picklist values found for ${fieldsEntityName}`);
      return 0;
    }

    const activeFields = fieldIdField.picklistValues.filter((p: any) => p.isActive);
    let registered = 0;

    // Register each available field as both a trigger and display-always field
    for (const field of activeFields) {
      try {
        const addFieldUrl = `${this.baseUrl}/${webhookEntityName}/${webhookId}/Fields`;
        await this.apiCall(addFieldUrl, 'POST', {
          FieldID: parseInt(field.value),
          IsDisplayAlwaysField: true,
          IsSubscribedField: true,
          WebhookID: webhookId,
        });
        registered++;
      } catch (error) {
        // Some fields may not support being triggers — that's OK
        const msg = error instanceof Error ? error.message : String(error);
        if (!msg.includes('already exists')) {
          console.debug(`[WEBHOOK-MGR] Could not add field ${field.label} (${field.value}): ${msg}`);
        }
      }
    }

    console.log(`[WEBHOOK-MGR] Registered ${registered}/${activeFields.length} trigger fields for webhook ${webhookId}`);
    return registered;
  }

  /**
   * Exclude the API integration resource from triggering webhooks (prevents infinite loops)
   */
  private async excludeApiResource(webhookEntityName: string, webhookId: number): Promise<number> {
    // Find the API resource by looking up the username
    const apiUsername = process.env.AUTOTASK_USERNAME || '';
    if (!apiUsername) return 0;

    try {
      const resources = await this.client.getFieldInfo('Resources');
      // The API user is typically an API-only resource; we look it up by email
      const resource = await this.client.getResourceByEmail(apiUsername.split('@')[0] + '@' + apiUsername.split('@')[1]?.replace('@', ''));

      if (!resource) {
        console.warn(`[WEBHOOK-MGR] Could not find API resource for ${apiUsername}`);
        return 0;
      }

      const excludeUrl = `${this.baseUrl}/${webhookEntityName}/${webhookId}/ExcludedResources`;
      await this.apiCall(excludeUrl, 'POST', {
        ResourceID: resource.id,
        WebhookID: webhookId,
      });

      console.log(`[WEBHOOK-MGR] Excluded resource ${resource.id} (${apiUsername}) from webhook ${webhookId}`);
      return 1;
    } catch (error) {
      console.warn(`[WEBHOOK-MGR] Failed to exclude API resource:`, error);
      return 0;
    }
  }

  /**
   * List all webhooks for an entity type
   */
  async listWebhooks(entityType: WebhookEntityType): Promise<any[]> {
    const webhookEntityName = getWebhookEntityName(entityType);
    const url = `${this.baseUrl}/${webhookEntityName}/query`;

    try {
      const response = await this.apiCall<{ items: any[] }>(url, 'POST', {
        filter: [{ op: 'gte', field: 'id', value: 0 }],
      });
      return response.items || [];
    } catch (error) {
      console.error(`[WEBHOOK-MGR] Failed to list webhooks for ${entityType}:`, error);
      return [];
    }
  }

  /**
   * Delete a webhook from Autotask
   */
  async deleteWebhook(entityType: WebhookEntityType, webhookId: number): Promise<void> {
    const webhookEntityName = getWebhookEntityName(entityType);
    const url = `${this.baseUrl}/${webhookEntityName}/${webhookId}`;

    try {
      await this.apiCall(url, 'DELETE');
      console.log(`[WEBHOOK-MGR] Deleted webhook ${webhookId} for ${entityType}`);
    } catch (error) {
      console.warn(`[WEBHOOK-MGR] Failed to delete webhook ${webhookId}:`, error);
    }

    // Remove from database
    await postgresClient.query(
      `UPDATE webhook_configs SET autotask_webhook_id = NULL WHERE entity_type = $1`,
      [entityType]
    );
  }

  /**
   * Deregister all webhooks
   */
  async deregisterAll(): Promise<void> {
    for (const entityType of WEBHOOK_SUPPORTED_ENTITIES) {
      const webhookId = await this.getExistingWebhookId(entityType);
      if (webhookId) {
        await this.deleteWebhook(entityType, webhookId);
      }
    }
  }

  /**
   * Get status of all registered webhooks
   */
  async getStatus(): Promise<Array<{
    entityType: WebhookEntityType;
    webhookId: number | null;
    isActive: boolean;
    isConfigured: boolean;
  }>> {
    const results = [];

    for (const entityType of WEBHOOK_SUPPORTED_ENTITIES) {
      const config = await postgresClient.query<{
        autotask_webhook_id: string | null;
        is_active: boolean;
      }>(
        `SELECT autotask_webhook_id, is_active FROM webhook_configs WHERE entity_type = $1`,
        [entityType]
      );

      const row = config.rows[0];
      results.push({
        entityType,
        webhookId: row?.autotask_webhook_id ? parseInt(row.autotask_webhook_id) : null,
        isActive: row?.is_active ?? false,
        isConfigured: !!row?.autotask_webhook_id,
      });
    }

    return results;
  }

  // --- Private helpers ---

  private async getExistingWebhookId(entityType: WebhookEntityType): Promise<number | null> {
    const result = await postgresClient.query<{ autotask_webhook_id: string }>(
      `SELECT autotask_webhook_id FROM webhook_configs WHERE entity_type = $1 AND autotask_webhook_id IS NOT NULL`,
      [entityType]
    );
    if (result.rows.length > 0 && result.rows[0].autotask_webhook_id) {
      return parseInt(result.rows[0].autotask_webhook_id);
    }
    return null;
  }

  private async saveWebhookConfig(entityType: WebhookEntityType, webhookId: number): Promise<void> {
    await postgresClient.query(
      `INSERT INTO webhook_configs (entity_type, event_types, is_active, autotask_webhook_id)
       VALUES ($1, $2, true, $3)
       ON CONFLICT (entity_type) DO UPDATE SET
         autotask_webhook_id = $3,
         is_active = true,
         updated_at = NOW()`,
      [entityType, JSON.stringify(['create', 'update', 'delete']), String(webhookId)]
    );
  }

  private async apiCall<T = any>(url: string, method: string, body?: any): Promise<T> {
    const headers: Record<string, string> = {
      'Username': process.env.AUTOTASK_USERNAME || '',
      'Secret': process.env.AUTOTASK_SECRET || '',
      'APIIntegrationcode': process.env.AUTOTASK_API_INTEGRATION_CODE || '',
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    };

    const options: RequestInit = { method, headers };
    if (body && method !== 'GET' && method !== 'DELETE') {
      options.body = JSON.stringify(body);
    }

    const response = await fetch(url, options);

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`Autotask API ${method} ${url} failed (${response.status}): ${errorText}`);
    }

    // DELETE returns no body
    if (method === 'DELETE') {
      return {} as T;
    }

    return response.json();
  }
}
