import { Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import axios, { AxiosInstance, AxiosError } from 'axios';
import {
  IntegrationConnectionError,
  IntegrationAuthError,
  IntegrationRateLimitError,
  IntegrationApiError,
  IntegrationConfigError,
} from '../../errors';
import { CredentialsService } from '../../credentials/credentials.service';

/**
 * Health status of a connector
 */
export interface ConnectorHealth {
  status: 'connected' | 'error' | 'not_configured';
  lastCheck: Date;
  latency?: number;
  error?: string;
  details?: Record<string, unknown>;
}

/**
 * Retry configuration
 */
interface RetryConfig {
  maxRetries: number;
  baseDelayMs: number;
  maxDelayMs: number;
  jitterFactor: number;
}

const DEFAULT_RETRY_CONFIG: RetryConfig = {
  maxRetries: 3,
  baseDelayMs: 1000,
  maxDelayMs: 30000,
  jitterFactor: 0.1,
};

const DEFAULT_TIMEOUT_MS = 30000;
import {
  FreeScoutConversation,
  FreeScoutCustomer,
  FreeScoutMailbox,
  FreeScoutTag,
  FreeScoutListResponse,
  ListConversationsDto,
  CreateConversationDto,
  ReplyToConversationDto,
  CreateCustomerDto,
  ListCustomersDto,
  ThreadType,
} from './freescout.types';

/**
 * FreeScout API Connector
 *
 * Provides integration with FreeScout helpdesk system.
 * Credentials are loaded lazily from the DB via CredentialsService.
 *
 * API Documentation: https://github.com/freescout-helpdesk/freescout/wiki/API
 */
@Injectable()
export class FreeScoutConnector {
  private readonly logger = new Logger(FreeScoutConnector.name);
  private readonly integrationName = 'FreeScout';
  private httpClient: AxiosInstance;
  private readonly retryConfig: RetryConfig;
  private configuredState: boolean = false;
  private initialized = false;

  private baseUrl: string = '';
  private apiKey: string = '';

  constructor(private readonly credentialsService: CredentialsService) {
    this.retryConfig = { ...DEFAULT_RETRY_CONFIG };
    // httpClient will be initialized lazily on first ensureInitialized() call
    this.httpClient = axios.create(); // placeholder, replaced on init
  }

  /**
   * Load credentials from DB and configure the connector.
   * Idempotent - returns immediately if already initialized.
   */
  async ensureInitialized(): Promise<void> {
    if (this.initialized) return;

    const result = await this.credentialsService.findActiveByType('FREESCOUT');

    if (!result) {
      this.configuredState = false;
      this.initialized = true;
      this.logger.warn('FreeScout connector not configured - no active credentials found');
      return;
    }

    this.baseUrl = result.credentials.apiUrl || '';
    this.apiKey = result.credentials.apiKey || '';

    this.configuredState = !!(this.baseUrl && this.apiKey);

    if (this.configuredState) {
      this.httpClient = axios.create({
        baseURL: `${this.baseUrl}/api`,
        timeout: DEFAULT_TIMEOUT_MS,
        headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json',
          'X-FreeScout-API-Key': this.apiKey,
        },
      });

      this.setupInterceptors();
      this.logger.log(`FreeScout connector initialized with base URL: ${this.baseUrl}`);
    } else {
      this.logger.warn('FreeScout connector not configured - missing apiUrl or apiKey in credentials');
    }

    this.initialized = true;
  }

  /**
   * Reload credentials from DB (called when credentials change)
   */
  async reload(): Promise<void> {
    this.initialized = false;
    this.configuredState = false;
    await this.ensureInitialized();
  }

  /**
   * Handle credential change events
   */
  @OnEvent('credentials.changed')
  async onCredentialsChanged(payload: { type: string }): Promise<void> {
    if (payload.type === 'FREESCOUT') {
      this.logger.log('FreeScout credentials changed, reloading');
      await this.reload();
    }
  }

  /**
   * Setup axios interceptors for logging
   */
  private setupInterceptors(): void {
    this.httpClient.interceptors.request.use(
      (config) => {
        this.logger.debug(`[${this.integrationName}] Request: ${config.method?.toUpperCase()} ${config.url}`);
        return config;
      },
      (error) => {
        this.logger.error(`[${this.integrationName}] Request error: ${error.message}`);
        return Promise.reject(error);
      },
    );

    this.httpClient.interceptors.response.use(
      (response) => {
        this.logger.debug(`[${this.integrationName}] Response: ${response.status} ${response.config.url}`);
        return response;
      },
      (error) => {
        if (error.response) {
          this.logger.warn(
            `[${this.integrationName}] Response error: ${error.response.status} ${error.config?.url}`,
          );
        }
        return Promise.reject(error);
      },
    );
  }

  /**
   * Ensure the connector is configured before making API calls
   */
  private ensureConfigured(): void {
    if (!this.configuredState) {
      throw new IntegrationConfigError(this.integrationName, ['apiUrl', 'apiKey']);
    }
  }

  /**
   * Execute request with retry logic
   */
  private async executeWithRetry<T>(
    requestFn: () => Promise<T>,
    operation: string,
  ): Promise<T> {
    let lastError: Error | undefined;
    let attempt = 0;

    while (attempt <= this.retryConfig.maxRetries) {
      try {
        return await requestFn();
      } catch (error) {
        lastError = error as Error;
        attempt++;

        const shouldRetry = this.shouldRetry(error as AxiosError, attempt);
        if (!shouldRetry) {
          break;
        }

        const delay = this.calculateDelay(attempt);
        this.logger.warn(
          `[${this.integrationName}] ${operation} failed (attempt ${attempt}/${this.retryConfig.maxRetries}), retrying in ${delay}ms`,
        );

        await this.sleep(delay);
      }
    }

    throw this.mapError(lastError as AxiosError, operation);
  }

  /**
   * Determine if request should be retried
   */
  private shouldRetry(error: AxiosError, attempt: number): boolean {
    if (attempt > this.retryConfig.maxRetries) {
      return false;
    }

    if (!error.response) {
      return true;
    }

    const status = error.response.status;
    return status >= 500 || status === 429;
  }

  /**
   * Calculate delay with exponential backoff and jitter
   */
  private calculateDelay(attempt: number): number {
    const delay = Math.min(
      this.retryConfig.baseDelayMs * Math.pow(2, attempt - 1),
      this.retryConfig.maxDelayMs,
    );
    const jitter = delay * this.retryConfig.jitterFactor * Math.random();
    return Math.floor(delay + jitter);
  }

  /**
   * Map axios errors to integration errors
   */
  private mapError(error: AxiosError, operation: string): Error {
    if (!error.response) {
      if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) {
        return new IntegrationConnectionError(
          this.integrationName,
          `Request timeout during ${operation}`,
          error,
        );
      }
      return new IntegrationConnectionError(
        this.integrationName,
        `Connection failed during ${operation}: ${error.message}`,
        error,
      );
    }

    const status = error.response.status;
    const responseData = error.response.data as Record<string, unknown>;
    const message = (responseData?.message as string) || (responseData?.error as string) || error.message;

    switch (status) {
      case 401:
        return new IntegrationAuthError(
          this.integrationName,
          `Authentication failed: Invalid API key`,
          error,
        );
      case 403:
        return new IntegrationAuthError(
          this.integrationName,
          `Access forbidden: ${message}`,
          error,
        );
      case 429: {
        const retryAfter = parseInt(error.response.headers['retry-after'] || '60', 10);
        return new IntegrationRateLimitError(
          this.integrationName,
          `Rate limit exceeded`,
          retryAfter,
          error,
        );
      }
      default:
        return new IntegrationApiError(
          this.integrationName,
          `${operation} failed: ${message}`,
          status,
          responseData?.code as string,
          error,
        );
    }
  }

  /**
   * Sleep helper
   */
  private sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  // ============ Health & Connection ============

  /**
   * Check connector health
   */
  async checkHealth(): Promise<ConnectorHealth> {
    await this.ensureInitialized();

    if (!this.configuredState) {
      return {
        status: 'not_configured',
        lastCheck: new Date(),
        error: 'FreeScout connector is not configured',
      };
    }

    const startTime = Date.now();
    try {
      await this.httpClient.get('/mailboxes', { params: { page: 1, pageSize: 1 } });
      return {
        status: 'connected',
        lastCheck: new Date(),
        latency: Date.now() - startTime,
      };
    } catch (error) {
      return {
        status: 'error',
        lastCheck: new Date(),
        latency: Date.now() - startTime,
        error: (error as Error).message,
      };
    }
  }

  /**
   * Test connection to FreeScout API
   */
  async testConnection(): Promise<{ success: boolean; message: string; latency?: number }> {
    await this.ensureInitialized();
    this.ensureConfigured();

    const startTime = Date.now();
    try {
      const response = await this.httpClient.get('/mailboxes', { params: { page: 1 } });
      const latency = Date.now() - startTime;

      const mailboxCount = response.data?._embedded?.mailboxes?.length || 0;

      return {
        success: true,
        message: `Successfully connected to FreeScout. Found ${mailboxCount} mailbox(es).`,
        latency,
      };
    } catch (error) {
      throw this.mapError(error as AxiosError, 'testConnection');
    }
  }

  /**
   * Check if connector is configured (last known state)
   */
  getIsConfigured(): boolean {
    return this.configuredState;
  }

  // ============ Conversations API ============

  /**
   * List conversations with optional filters
   */
  async listConversations(params: ListConversationsDto = {}): Promise<{
    conversations: FreeScoutConversation[];
    pagination: { page: number; pageSize: number; total: number; totalPages: number };
  }> {
    await this.ensureInitialized();
    this.ensureConfigured();

    return this.executeWithRetry(async () => {
      const queryParams: Record<string, unknown> = {
        page: params.page || 1,
      };

      if (params.mailboxId) {
        queryParams.mailbox = params.mailboxId;
      }
      if (params.status) {
        queryParams.status = params.status;
      }
      if (params.assignedTo) {
        queryParams.assignedTo = params.assignedTo;
      }
      if (params.tag) {
        queryParams.tag = params.tag;
      }

      const response = await this.httpClient.get<FreeScoutListResponse<FreeScoutConversation>>(
        '/conversations',
        { params: queryParams },
      );

      const conversations = response.data._embedded?.conversations || [];
      const page = response.data.page;

      return {
        conversations,
        pagination: {
          page: page.current_page,
          pageSize: page.per_page,
          total: page.total,
          totalPages: page.last_page,
        },
      };
    }, 'listConversations');
  }

  /**
   * Get a single conversation by ID
   */
  async getConversation(id: number): Promise<FreeScoutConversation> {
    await this.ensureInitialized();
    this.ensureConfigured();

    return this.executeWithRetry(async () => {
      const response = await this.httpClient.get<FreeScoutConversation>(`/conversations/${id}`);
      return response.data;
    }, 'getConversation');
  }

  /**
   * Create a new conversation
   */
  async createConversation(data: CreateConversationDto): Promise<FreeScoutConversation> {
    await this.ensureInitialized();
    this.ensureConfigured();

    return this.executeWithRetry(async () => {
      const payload = {
        type: data.type,
        subject: data.subject,
        mailboxId: data.mailboxId,
        customer: {
          email: data.customerEmail,
          firstName: data.customerFirstName,
          lastName: data.customerLastName,
        },
        threads: [
          {
            type: ThreadType.CUSTOMER,
            body: data.body,
          },
        ],
        status: data.status || 'active',
        tags: data.tags,
        assignTo: data.assignTo,
      };

      const response = await this.httpClient.post<FreeScoutConversation>('/conversations', payload);
      return response.data;
    }, 'createConversation');
  }

  /**
   * Reply to an existing conversation
   */
  async replyToConversation(
    conversationId: number,
    data: ReplyToConversationDto,
  ): Promise<FreeScoutConversation> {
    await this.ensureInitialized();
    this.ensureConfigured();

    return this.executeWithRetry(async () => {
      const payload = {
        type: data.type || ThreadType.MESSAGE,
        body: data.body,
        user: data.userId,
        status: data.status,
        draft: data.draft || false,
        cc: data.cc,
        bcc: data.bcc,
      };

      const response = await this.httpClient.post<FreeScoutConversation>(
        `/conversations/${conversationId}/threads`,
        payload,
      );
      return response.data;
    }, 'replyToConversation');
  }

  /**
   * Update conversation status
   */
  async updateConversationStatus(
    conversationId: number,
    status: string,
  ): Promise<FreeScoutConversation> {
    await this.ensureInitialized();
    this.ensureConfigured();

    return this.executeWithRetry(async () => {
      const response = await this.httpClient.put<FreeScoutConversation>(
        `/conversations/${conversationId}`,
        { status },
      );
      return response.data;
    }, 'updateConversationStatus');
  }

  // ============ Mailboxes API ============

  /**
   * List all mailboxes
   */
  async listMailboxes(): Promise<FreeScoutMailbox[]> {
    await this.ensureInitialized();
    this.ensureConfigured();

    return this.executeWithRetry(async () => {
      const response = await this.httpClient.get<FreeScoutListResponse<FreeScoutMailbox>>('/mailboxes');
      return response.data._embedded?.mailboxes || [];
    }, 'listMailboxes');
  }

  /**
   * Get a single mailbox by ID
   */
  async getMailbox(id: number): Promise<FreeScoutMailbox> {
    await this.ensureInitialized();
    this.ensureConfigured();

    return this.executeWithRetry(async () => {
      const response = await this.httpClient.get<FreeScoutMailbox>(`/mailboxes/${id}`);
      return response.data;
    }, 'getMailbox');
  }

  // ============ Customers API ============

  /**
   * List customers with optional filters
   */
  async listCustomers(params: ListCustomersDto = {}): Promise<{
    customers: FreeScoutCustomer[];
    pagination: { page: number; pageSize: number; total: number; totalPages: number };
  }> {
    await this.ensureInitialized();
    this.ensureConfigured();

    return this.executeWithRetry(async () => {
      const queryParams: Record<string, unknown> = {
        page: params.page || 1,
      };

      if (params.email) {
        queryParams.email = params.email;
      }
      if (params.query) {
        queryParams.query = params.query;
      }

      const response = await this.httpClient.get<FreeScoutListResponse<FreeScoutCustomer>>(
        '/customers',
        { params: queryParams },
      );

      const customers = response.data._embedded?.customers || [];
      const page = response.data.page;

      return {
        customers,
        pagination: {
          page: page.current_page,
          pageSize: page.per_page,
          total: page.total,
          totalPages: page.last_page,
        },
      };
    }, 'listCustomers');
  }

  /**
   * Get a single customer by ID
   */
  async getCustomer(id: number): Promise<FreeScoutCustomer> {
    await this.ensureInitialized();
    this.ensureConfigured();

    return this.executeWithRetry(async () => {
      const response = await this.httpClient.get<FreeScoutCustomer>(`/customers/${id}`);
      return response.data;
    }, 'getCustomer');
  }

  /**
   * Create a new customer
   */
  async createCustomer(data: CreateCustomerDto): Promise<FreeScoutCustomer> {
    await this.ensureInitialized();
    this.ensureConfigured();

    return this.executeWithRetry(async () => {
      const response = await this.httpClient.post<FreeScoutCustomer>('/customers', data);
      return response.data;
    }, 'createCustomer');
  }

  /**
   * Find customer by email
   */
  async findCustomerByEmail(email: string): Promise<FreeScoutCustomer | null> {
    await this.ensureInitialized();
    this.ensureConfigured();

    const result = await this.listCustomers({ email });
    return result.customers[0] || null;
  }

  // ============ Tags API ============

  /**
   * List all tags
   */
  async listTags(): Promise<FreeScoutTag[]> {
    await this.ensureInitialized();
    this.ensureConfigured();

    return this.executeWithRetry(async () => {
      const response = await this.httpClient.get<FreeScoutListResponse<FreeScoutTag>>('/tags');
      return response.data._embedded?.tags || [];
    }, 'listTags');
  }

  /**
   * Add tags to a conversation
   */
  async addTagsToConversation(conversationId: number, tags: string[]): Promise<void> {
    await this.ensureInitialized();
    this.ensureConfigured();

    return this.executeWithRetry(async () => {
      await this.httpClient.put(`/conversations/${conversationId}`, { tags });
    }, 'addTagsToConversation');
  }
}
