import {
  ExternalCallConfig,
  ExternalCallResult,
  ExternalCallRequest,
  SlackSettings,
  KakaoTalkSettings,
  DiscordSettings,
  GenericApiSettings,
  EmailSettings,
  SupportedExternalCallSettings,
  TemplateOptions,
} from "../types/externalCallTypes";
import { DataMappingService } from "./dataMappingService";
import {
  DataMappingConfig,
  DataMappingResult,
} from "../types/dataMappingTypes";

/**
 * 외부 호출 서비스
 * REST API, 웹훅, 이메일 등 다양한 외부 시스템 호출을 처리
 */
export class ExternalCallService {
  private readonly DEFAULT_TIMEOUT = 30000; // 30초
  private readonly DEFAULT_RETRY_COUNT = 3;
  private dataMappingService: DataMappingService;
  private readonly DEFAULT_RETRY_DELAY = 1000; // 1초

  constructor() {
    this.dataMappingService = new DataMappingService();
  }

  /**
   * 데이터 매핑과 함께 외부 호출 실행
   */
  async executeWithDataMapping(
    config: ExternalCallConfig,
    dataMappingConfig?: DataMappingConfig,
    triggerData?: any
  ): Promise<{
    callResult: ExternalCallResult;
    mappingResult?: DataMappingResult;
  }> {
    const startTime = Date.now();

    console.log(`🚀 [ExternalCallService] 데이터 매핑 포함 외부 호출 시작:`, {
      callType: config.callType,
      hasMappingConfig: !!dataMappingConfig,
      mappingDirection: dataMappingConfig?.direction,
    });

    try {
      let requestData = config;

      // Outbound 매핑 처리 (내부 → 외부)
      if (
        dataMappingConfig?.direction === "outbound" &&
        dataMappingConfig.outboundMapping
      ) {
        console.log(`📤 [ExternalCallService] Outbound 매핑 처리 시작`);

        const outboundData = await this.dataMappingService.processOutboundData(
          dataMappingConfig.outboundMapping,
          triggerData
        );

        // API 요청 바디에 매핑된 데이터 포함
        if (config.callType === "rest-api") {
          // GenericApiSettings로 타입 캐스팅
          const apiConfig = config as GenericApiSettings;
          const bodyTemplate = apiConfig.body || "{}";

          // 템플릿에 데이터 삽입
          const processedBody = this.processTemplate(bodyTemplate, {
            mappedData: outboundData,
            triggerData,
            ...outboundData,
          });

          requestData = {
            ...config,
            body: processedBody,
          } as GenericApiSettings;
        }
      }

      // 외부 호출 실행
      const callRequest: ExternalCallRequest = {
        diagramId: 0, // 임시값
        relationshipId: "data-mapping", // 임시값
        settings: requestData,
        templateData: triggerData,
      };
      const callResult = await this.executeExternalCall(callRequest);

      let mappingResult: DataMappingResult | undefined;

      // Inbound 매핑 처리 (외부 → 내부)
      if (
        callResult.success &&
        dataMappingConfig?.direction === "inbound" &&
        dataMappingConfig.inboundMapping
      ) {
        console.log(`📥 [ExternalCallService] Inbound 매핑 처리 시작`);

        try {
          // 응답 데이터 파싱
          let responseData = callResult.response;
          if (typeof responseData === "string") {
            try {
              responseData = JSON.parse(responseData);
            } catch {
              console.warn(
                `⚠️ [ExternalCallService] 응답 데이터 JSON 파싱 실패, 문자열로 처리`
              );
            }
          }

          mappingResult = await this.dataMappingService.processInboundData(
            responseData,
            dataMappingConfig.inboundMapping
          );

          console.log(`✅ [ExternalCallService] Inbound 매핑 완료:`, {
            recordsProcessed: mappingResult.recordsProcessed,
            recordsInserted: mappingResult.recordsInserted,
          });
        } catch (error) {
          console.error(`❌ [ExternalCallService] Inbound 매핑 실패:`, error);
          mappingResult = {
            success: false,
            direction: "inbound",
            errors: [error instanceof Error ? error.message : String(error)],
            executionTime: Date.now() - startTime,
            timestamp: new Date().toISOString(),
          };
        }
      }

      // 양방향 매핑 처리
      if (dataMappingConfig?.direction === "bidirectional") {
        // 필요한 경우 양방향 매핑 로직 구현
        console.log(`🔄 [ExternalCallService] 양방향 매핑은 향후 구현 예정`);
      }

      const result = {
        callResult,
        mappingResult,
      };

      console.log(`✅ [ExternalCallService] 데이터 매핑 포함 외부 호출 완료:`, {
        callSuccess: callResult.success,
        mappingSuccess: mappingResult?.success,
        totalExecutionTime: Date.now() - startTime,
      });

      return result;
    } catch (error) {
      console.error(
        `❌ [ExternalCallService] 데이터 매핑 포함 외부 호출 실패:`,
        error
      );
      throw error;
    }
  }

  /**
   * 기존 외부 호출 실행 (매핑 없음)
   */
  async executeExternalCall(
    request: ExternalCallRequest
  ): Promise<ExternalCallResult> {
    const startTime = Date.now();

    try {
      let result: ExternalCallResult;

      switch (request.settings.callType) {
        case "rest-api":
          result = await this.executeRestApiCall(request);
          break;
        case "email":
          result = await this.executeEmailCall(request);
          break;
        case "ftp":
          throw new Error("FTP 호출은 아직 구현되지 않았습니다.");
        case "queue":
          throw new Error("메시지 큐 호출은 아직 구현되지 않았습니다.");
        default:
          throw new Error(
            `지원되지 않는 호출 타입: ${request.settings.callType}`
          );
      }

      result.executionTime = Date.now() - startTime;
      result.timestamp = new Date();

      return result;
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error),
        executionTime: Date.now() - startTime,
        timestamp: new Date(),
      };
    }
  }

  /**
   * REST API 호출 실행
   */
  private async executeRestApiCall(
    request: ExternalCallRequest
  ): Promise<ExternalCallResult> {
    const settings = request.settings as any; // 임시로 any 사용

    switch (settings.apiType) {
      case "slack":
        return await this.executeSlackWebhook(
          settings as SlackSettings,
          request.templateData
        );
      case "kakao-talk":
        return await this.executeKakaoTalkApi(
          settings as KakaoTalkSettings,
          request.templateData
        );
      case "discord":
        return await this.executeDiscordWebhook(
          settings as DiscordSettings,
          request.templateData
        );
      case "generic":
      default:
        return await this.executeGenericApi(
          settings as GenericApiSettings,
          request.templateData
        );
    }
  }

  /**
   * 슬랙 웹훅 실행
   */
  private async executeSlackWebhook(
    settings: SlackSettings,
    templateData?: Record<string, unknown>
  ): Promise<ExternalCallResult> {
    const payload = {
      text: this.processTemplate(settings.message, templateData),
      channel: settings.channel,
      username: settings.username || "DataFlow Bot",
      icon_emoji: settings.iconEmoji || ":robot_face:",
    };

    return await this.makeHttpRequest({
      url: settings.webhookUrl,
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
      timeout: settings.timeout || this.DEFAULT_TIMEOUT,
    });
  }

  /**
   * 카카오톡 API 실행
   */
  private async executeKakaoTalkApi(
    settings: KakaoTalkSettings,
    templateData?: Record<string, unknown>
  ): Promise<ExternalCallResult> {
    const payload = {
      object_type: "text",
      text: this.processTemplate(settings.message, templateData),
      link: {
        web_url: "https://developers.kakao.com",
        mobile_web_url: "https://developers.kakao.com",
      },
    };

    return await this.makeHttpRequest({
      url: "https://kapi.kakao.com/v2/api/talk/memo/default/send",
      method: "POST",
      headers: {
        Authorization: `Bearer ${settings.accessToken}`,
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: `template_object=${encodeURIComponent(JSON.stringify(payload))}`,
      timeout: settings.timeout || this.DEFAULT_TIMEOUT,
    });
  }

  /**
   * 디스코드 웹훅 실행
   */
  private async executeDiscordWebhook(
    settings: DiscordSettings,
    templateData?: Record<string, unknown>
  ): Promise<ExternalCallResult> {
    const payload = {
      content: this.processTemplate(settings.message, templateData),
      username: settings.username || "시스템 알리미",
      avatar_url: settings.avatarUrl,
    };

    return await this.makeHttpRequest({
      url: settings.webhookUrl,
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
      timeout: settings.timeout || this.DEFAULT_TIMEOUT,
    });
  }

  /**
   * 일반 REST API 실행
   */
  private async executeGenericApi(
    settings: GenericApiSettings,
    templateData?: Record<string, unknown>
  ): Promise<ExternalCallResult> {
    let body = settings.body;
    if (body && templateData) {
      body = this.processTemplate(body, templateData);
    }

    // 기본 헤더 준비
    const headers = { ...(settings.headers || {}) };

    // 인증 정보 처리
    if (settings.authentication) {
      switch (settings.authentication.type) {
        case "api-key":
          if (settings.authentication.apiKey) {
            headers["X-API-Key"] = settings.authentication.apiKey;
          }
          break;
        case "basic":
          if (
            settings.authentication.username &&
            settings.authentication.password
          ) {
            const credentials = Buffer.from(
              `${settings.authentication.username}:${settings.authentication.password}`
            ).toString("base64");
            headers["Authorization"] = `Basic ${credentials}`;
          }
          break;
        case "bearer":
          if (settings.authentication.token) {
            headers["Authorization"] =
              `Bearer ${settings.authentication.token}`;
          }
          break;
        case "custom":
          if (
            settings.authentication.headerName &&
            settings.authentication.headerValue
          ) {
            headers[settings.authentication.headerName] =
              settings.authentication.headerValue;
          }
          break;
        // 'none' 타입은 아무것도 하지 않음
      }
    }

    console.log(`🔐 [ExternalCallService] 인증 처리 완료:`, {
      authType: settings.authentication?.type || "none",
      hasAuthHeader: !!headers["Authorization"],
      headers: Object.keys(headers),
    });

    return await this.makeHttpRequest({
      url: settings.url,
      method: settings.method,
      headers: headers,
      body: body,
      timeout: settings.timeout || this.DEFAULT_TIMEOUT,
    });
  }

  /**
   * 이메일 호출 실행 (향후 구현)
   */
  private async executeEmailCall(
    request: ExternalCallRequest
  ): Promise<ExternalCallResult> {
    // TODO: 이메일 발송 구현 (Java MailUtil 연동)
    throw new Error("이메일 발송 기능은 아직 구현되지 않았습니다.");
  }

  /**
   * HTTP 요청 실행 (공통)
   */
  private async makeHttpRequest(options: {
    url: string;
    method: string;
    headers?: Record<string, string>;
    body?: string;
    timeout: number;
  }): Promise<ExternalCallResult> {
    try {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), options.timeout);

      // GET, HEAD 메서드는 body를 가질 수 없음
      const method = options.method.toUpperCase();
      const requestOptions: RequestInit = {
        method: options.method,
        headers: options.headers,
        signal: controller.signal,
      };

      // GET, HEAD 메서드가 아닌 경우에만 body 추가
      if (method !== "GET" && method !== "HEAD" && options.body) {
        requestOptions.body = options.body;
      }

      const response = await fetch(options.url, requestOptions);

      clearTimeout(timeoutId);

      const responseText = await response.text();

      // 디버깅을 위한 로그 추가
      console.log(`🔍 [ExternalCallService] HTTP 응답:`, {
        url: options.url,
        method: options.method,
        status: response.status,
        statusText: response.statusText,
        ok: response.ok,
        headers: Object.fromEntries(response.headers.entries()),
        responseText: responseText.substring(0, 500), // 처음 500자만 로그
      });

      return {
        success: response.ok,
        statusCode: response.status,
        response: responseText,
        executionTime: 0, // 상위에서 설정됨
        timestamp: new Date(),
      };
    } catch (error) {
      if (error instanceof Error) {
        if (error.name === "AbortError") {
          throw new Error(`요청 시간 초과 (${options.timeout}ms)`);
        }
        throw error;
      }
      throw new Error(`HTTP 요청 실패: ${String(error)}`);
    }
  }

  /**
   * 템플릿 문자열 처리
   */
  private processTemplate(
    template: string,
    data?: Record<string, unknown>,
    options: TemplateOptions = {}
  ): string {
    if (!data || Object.keys(data).length === 0) {
      return template;
    }

    const startDelimiter = options.startDelimiter || "{{";
    const endDelimiter = options.endDelimiter || "}}";

    let result = template;

    Object.entries(data).forEach(([key, value]) => {
      const placeholder = `${startDelimiter}${key}${endDelimiter}`;
      const replacement = String(value ?? "");
      result = result.replace(new RegExp(placeholder, "g"), replacement);
    });

    return result;
  }

  /**
   * 외부 호출 설정 검증
   */
  validateSettings(settings: SupportedExternalCallSettings): {
    valid: boolean;
    errors: string[];
  } {
    const errors: string[] = [];

    if (settings.callType === "rest-api") {
      switch (settings.apiType) {
        case "slack":
          const slackSettings = settings as SlackSettings;
          if (!slackSettings.webhookUrl)
            errors.push("슬랙 웹훅 URL이 필요합니다.");
          if (!slackSettings.message) errors.push("슬랙 메시지가 필요합니다.");
          break;

        case "kakao-talk":
          const kakaoSettings = settings as KakaoTalkSettings;
          if (!kakaoSettings.accessToken)
            errors.push("카카오톡 액세스 토큰이 필요합니다.");
          if (!kakaoSettings.message)
            errors.push("카카오톡 메시지가 필요합니다.");
          break;

        case "discord":
          const discordSettings = settings as DiscordSettings;
          if (!discordSettings.webhookUrl)
            errors.push("디스코드 웹훅 URL이 필요합니다.");
          if (!discordSettings.message)
            errors.push("디스코드 메시지가 필요합니다.");
          break;

        case "generic":
        default:
          const genericSettings = settings as GenericApiSettings;
          if (!genericSettings.url) errors.push("API URL이 필요합니다.");
          if (!genericSettings.method) errors.push("HTTP 메서드가 필요합니다.");
          break;
      }
    } else if (settings.callType === "email") {
      const emailSettings = settings as EmailSettings;
      if (!emailSettings.smtpHost) errors.push("SMTP 호스트가 필요합니다.");
      if (!emailSettings.toEmail) errors.push("수신 이메일이 필요합니다.");
      if (!emailSettings.subject) errors.push("이메일 제목이 필요합니다.");
    }

    return {
      valid: errors.length === 0,
      errors,
    };
  }
}
