/**
 * DDL 실행 서비스
 * 실제 PostgreSQL 테이블 및 컬럼 생성을 담당
 */

import { query, queryOne, transaction } from "../database/db";
import {
  CreateColumnDefinition,
  DDLExecutionResult,
  WEB_TYPE_TO_POSTGRES_MAP,
  WebType,
} from "../types/ddl";
import { DDLSafetyValidator } from "./ddlSafetyValidator";
import { DDLAuditLogger } from "./ddlAuditLogger";
import { logger } from "../utils/logger";
import { cache, CacheKeys } from "../utils/cache";

export class DDLExecutionService {
  /**
   * 새 테이블 생성
   */
  async createTable(
    tableName: string,
    columns: CreateColumnDefinition[],
    userCompanyCode: string,
    userId: string,
    description?: string
  ): Promise<DDLExecutionResult> {
    // DDL 실행 시작 로그
    await DDLAuditLogger.logDDLStart(
      userId,
      userCompanyCode,
      "CREATE_TABLE",
      tableName,
      { columns, description }
    );

    try {
      // 1. 권한 검증
      this.validateSuperAdminPermission(userCompanyCode);

      // 2. 안전성 검증
      const validation = DDLSafetyValidator.validateTableCreation(
        tableName,
        columns
      );
      if (!validation.isValid) {
        const errorMessage = `테이블 생성 검증 실패: ${validation.errors.join(", ")}`;

        await DDLAuditLogger.logDDLExecution(
          userId,
          userCompanyCode,
          "CREATE_TABLE",
          tableName,
          "VALIDATION_FAILED",
          false,
          errorMessage
        );

        return {
          success: false,
          message: errorMessage,
          error: {
            code: "VALIDATION_FAILED",
            details: validation.errors.join(", "),
          },
        };
      }

      // 3. 테이블 존재 여부 확인
      const tableExists = await this.checkTableExists(tableName);
      if (tableExists) {
        const errorMessage = `테이블 '${tableName}'이 이미 존재합니다.`;

        await DDLAuditLogger.logDDLExecution(
          userId,
          userCompanyCode,
          "CREATE_TABLE",
          tableName,
          "TABLE_EXISTS",
          false,
          errorMessage
        );

        return {
          success: false,
          message: errorMessage,
          error: {
            code: "TABLE_EXISTS",
            details: errorMessage,
          },
        };
      }

      // 4. DDL 쿼리 생성
      const ddlQuery = this.generateCreateTableQuery(tableName, columns);

      // 5. 트랜잭션으로 안전하게 실행
      await transaction(async (client) => {
        // 5-1. 테이블 생성
        await client.query(ddlQuery);

        // 5-2. 테이블 메타데이터 저장
        await this.saveTableMetadata(client, tableName, description);

        // 5-3. 컬럼 메타데이터 저장
        await this.saveColumnMetadata(client, tableName, columns, userCompanyCode);
      });

      // 6. 성공 로그 기록
      await DDLAuditLogger.logDDLExecution(
        userId,
        userCompanyCode,
        "CREATE_TABLE",
        tableName,
        ddlQuery,
        true
      );

      logger.info("테이블 생성 성공", {
        tableName,
        userId,
        columnCount: columns.length,
      });

      // 테이블 생성 후 관련 캐시 무효화
      this.invalidateTableCache(tableName);

      return {
        success: true,
        message: `테이블 '${tableName}'이 성공적으로 생성되었습니다.`,
        executedQuery: ddlQuery,
      };
    } catch (error) {
      const errorMessage = `테이블 생성 실패: ${(error as Error).message}`;

      // 실패 로그 기록
      await DDLAuditLogger.logDDLExecution(
        userId,
        userCompanyCode,
        "CREATE_TABLE",
        tableName,
        `FAILED: ${(error as Error).message}`,
        false,
        errorMessage
      );

      logger.error("테이블 생성 실패:", {
        tableName,
        userId,
        error: (error as Error).message,
        stack: (error as Error).stack,
      });

      return {
        success: false,
        message: errorMessage,
        error: {
          code: "EXECUTION_FAILED",
          details: (error as Error).message,
        },
      };
    }
  }

  /**
   * 기존 테이블에 컬럼 추가
   */
  async addColumn(
    tableName: string,
    column: CreateColumnDefinition,
    userCompanyCode: string,
    userId: string
  ): Promise<DDLExecutionResult> {
    // DDL 실행 시작 로그
    await DDLAuditLogger.logDDLStart(
      userId,
      userCompanyCode,
      "ADD_COLUMN",
      tableName,
      { column }
    );

    try {
      // 1. 권한 검증
      this.validateSuperAdminPermission(userCompanyCode);

      // 2. 안전성 검증
      const validation = DDLSafetyValidator.validateColumnAddition(
        tableName,
        column
      );
      if (!validation.isValid) {
        const errorMessage = `컬럼 추가 검증 실패: ${validation.errors.join(", ")}`;

        await DDLAuditLogger.logDDLExecution(
          userId,
          userCompanyCode,
          "ADD_COLUMN",
          tableName,
          "VALIDATION_FAILED",
          false,
          errorMessage
        );

        return {
          success: false,
          message: errorMessage,
          error: {
            code: "VALIDATION_FAILED",
            details: validation.errors.join(", "),
          },
        };
      }

      // 3. 테이블 존재 여부 확인
      const tableExists = await this.checkTableExists(tableName);
      if (!tableExists) {
        const errorMessage = `테이블 '${tableName}'이 존재하지 않습니다.`;

        await DDLAuditLogger.logDDLExecution(
          userId,
          userCompanyCode,
          "ADD_COLUMN",
          tableName,
          "TABLE_NOT_EXISTS",
          false,
          errorMessage
        );

        return {
          success: false,
          message: errorMessage,
          error: {
            code: "TABLE_NOT_EXISTS",
            details: errorMessage,
          },
        };
      }

      // 4. 컬럼 존재 여부 확인
      const columnExists = await this.checkColumnExists(tableName, column.name);
      if (columnExists) {
        const errorMessage = `컬럼 '${column.name}'이 이미 존재합니다.`;

        await DDLAuditLogger.logDDLExecution(
          userId,
          userCompanyCode,
          "ADD_COLUMN",
          tableName,
          "COLUMN_EXISTS",
          false,
          errorMessage
        );

        return {
          success: false,
          message: errorMessage,
          error: {
            code: "COLUMN_EXISTS",
            details: errorMessage,
          },
        };
      }

      // 5. DDL 쿼리 생성
      const ddlQuery = this.generateAddColumnQuery(tableName, column);

      // 6. 트랜잭션으로 안전하게 실행
      await transaction(async (client) => {
        // 6-1. 컬럼 추가
        await client.query(ddlQuery);

        // 6-2. 컬럼 메타데이터 저장
        await this.saveColumnMetadata(client, tableName, [column], userCompanyCode);
      });

      // 7. 성공 로그 기록
      await DDLAuditLogger.logDDLExecution(
        userId,
        userCompanyCode,
        "ADD_COLUMN",
        tableName,
        ddlQuery,
        true
      );

      logger.info("컬럼 추가 성공", {
        tableName,
        columnName: column.name,
        webType: column.webType,
        userId,
      });

      // 컬럼 추가 후 관련 캐시 무효화
      this.invalidateTableCache(tableName);

      return {
        success: true,
        message: `컬럼 '${column.name}'이 성공적으로 추가되었습니다.`,
        executedQuery: ddlQuery,
      };
    } catch (error) {
      const errorMessage = `컬럼 추가 실패: ${(error as Error).message}`;

      // 실패 로그 기록
      await DDLAuditLogger.logDDLExecution(
        userId,
        userCompanyCode,
        "ADD_COLUMN",
        tableName,
        `FAILED: ${(error as Error).message}`,
        false,
        errorMessage
      );

      logger.error("컬럼 추가 실패:", {
        tableName,
        columnName: column.name,
        userId,
        error: (error as Error).message,
        stack: (error as Error).stack,
      });

      return {
        success: false,
        message: errorMessage,
        error: {
          code: "EXECUTION_FAILED",
          details: (error as Error).message,
        },
      };
    }
  }

  /**
   * CREATE TABLE DDL 쿼리 생성
   */
  private generateCreateTableQuery(
    tableName: string,
    columns: CreateColumnDefinition[]
  ): string {
    // 사용자 정의 컬럼들 - 모두 VARCHAR(500)로 통일
    const columnDefinitions = columns
      .map((col) => {
        // 입력 타입과 관계없이 모든 컬럼을 VARCHAR(500)로 생성
        let definition = `"${col.name}" varchar(500)`;

        if (!col.nullable) {
          definition += " NOT NULL";
        }

        if (col.defaultValue) {
          definition += ` DEFAULT '${col.defaultValue}'`;
        }

        return definition;
      })
      .join(",\n  ");

    // 기본 컬럼들 (날짜는 TIMESTAMP, 나머지는 VARCHAR)
    const baseColumns = `
  "id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text,
  "created_date" timestamp DEFAULT now(),
  "updated_date" timestamp DEFAULT now(),
  "writer" varchar(500) DEFAULT NULL,
  "company_code" varchar(500)`;

    // 최종 CREATE TABLE 쿼리
    return `
CREATE TABLE "${tableName}" (${baseColumns},
  ${columnDefinitions}
);`.trim();
  }

  /**
   * ALTER TABLE ADD COLUMN DDL 쿼리 생성
   */
  private generateAddColumnQuery(
    tableName: string,
    column: CreateColumnDefinition
  ): string {
    // 새로 추가되는 컬럼도 VARCHAR(500)로 통일
    let definition = `"${column.name}" varchar(500)`;

    if (!column.nullable) {
      definition += " NOT NULL";
    }

    if (column.defaultValue) {
      definition += ` DEFAULT '${column.defaultValue}'`;
    }

    return `ALTER TABLE "${tableName}" ADD COLUMN ${definition};`;
  }

  /**
   * 입력타입을 PostgreSQL 타입으로 매핑 (날짜는 TIMESTAMP, 나머지는 VARCHAR)
   * 날짜 타입만 TIMESTAMP로, 나머지는 VARCHAR(500)로 통일
   */
  private mapInputTypeToPostgresType(inputType?: string): string {
    switch (inputType) {
      case "date":
        return "timestamp";
      default:
        // 날짜 외의 모든 타입은 VARCHAR(500)로 통일
        return "varchar(500)";
    }
  }

  /**
   * 레거시 지원: 웹타입을 PostgreSQL 타입으로 매핑
   * @deprecated 새로운 시스템에서는 mapInputTypeToPostgresType 사용
   */
  private mapWebTypeToPostgresType(webType: WebType, length?: number): string {
    // 레거시 지원을 위해 유지하되, VARCHAR(500)로 통일
    logger.info(`레거시 웹타입 사용: ${webType} → varchar(500)로 변환`);
    return "varchar(500)";
  }

  /**
   * 테이블 메타데이터 저장
   */
  private async saveTableMetadata(
    client: any,
    tableName: string,
    description?: string
  ): Promise<void> {
    await client.query(
      `
      INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date)
      VALUES ($1, $2, $3, now(), now())
      ON CONFLICT (table_name) 
      DO UPDATE SET 
        table_label = $2,
        description = $3,
        updated_date = now()
      `,
      [tableName, tableName, description || `사용자 생성 테이블: ${tableName}`]
    );
  }

  /**
   * 컬럼 메타데이터 저장
   */
  private async saveColumnMetadata(
    client: any,
    tableName: string,
    columns: CreateColumnDefinition[],
    companyCode: string
  ): Promise<void> {
    // 먼저 table_labels에 테이블 정보가 있는지 확인하고 없으면 생성
    await client.query(
      `
      INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date)
      VALUES ($1, $2, $3, now(), now())
      ON CONFLICT (table_name) 
      DO UPDATE SET updated_date = now()
      `,
      [tableName, tableName, `자동 생성된 테이블 메타데이터: ${tableName}`]
    );

    // 기본 컬럼들 정의 (모든 테이블에 자동으로 추가되는 시스템 컬럼)
    const defaultColumns = [
      {
        name: "id",
        label: "ID",
        inputType: "text",
        description: "기본키 (자동생성)",
        order: -5,
        isVisible: true,
      },
      {
        name: "created_date",
        label: "생성일시",
        inputType: "date",
        description: "레코드 생성일시",
        order: -4,
        isVisible: true,
      },
      {
        name: "updated_date",
        label: "수정일시",
        inputType: "date",
        description: "레코드 수정일시",
        order: -3,
        isVisible: true,
      },
      {
        name: "writer",
        label: "작성자",
        inputType: "text",
        description: "레코드 작성자",
        order: -2,
        isVisible: true,
      },
      {
        name: "company_code",
        label: "회사코드",
        inputType: "text",
        description: "회사 구분 코드",
        order: -1,
        isVisible: true,
      },
    ];

    // 기본 컬럼들을 table_type_columns에 등록
    for (const defaultCol of defaultColumns) {
      await client.query(
        `
        INSERT INTO table_type_columns (
          table_name, column_name, company_code, input_type, detail_settings, 
          is_nullable, display_order, created_date, updated_date
        ) VALUES (
          $1, $2, $3, $4, '{}',
          'Y', $5, now(), now()
        )
        ON CONFLICT (table_name, column_name, company_code) 
        DO UPDATE SET 
          input_type = $4,
          display_order = $5,
          updated_date = now()
        `,
        [tableName, defaultCol.name, companyCode, defaultCol.inputType, defaultCol.order]
      );
    }

    // 사용자 정의 컬럼들을 table_type_columns에 등록
    for (let i = 0; i < columns.length; i++) {
      const column = columns[i];
      const inputType = this.convertWebTypeToInputType(
        column.webType || "text"
      );
      const detailSettings = JSON.stringify(column.detailSettings || {});

      await client.query(
        `
        INSERT INTO table_type_columns (
          table_name, column_name, company_code, input_type, detail_settings, 
          is_nullable, display_order, created_date, updated_date
        ) VALUES (
          $1, $2, $3, $4, $5,
          'Y', $6, now(), now()
        )
        ON CONFLICT (table_name, column_name, company_code) 
        DO UPDATE SET 
          input_type = $4,
          detail_settings = $5,
          display_order = $6,
          updated_date = now()
        `,
        [tableName, column.name, companyCode, inputType, detailSettings, i]
      );
    }

    // 레거시 column_labels 테이블 지원 제거됨 (2026-01-26)
    // 모든 컬럼 메타데이터는 table_type_columns에서 관리
  }

  /**
   * 웹 타입을 입력 타입으로 변환
   */
  private convertWebTypeToInputType(webType: string): string {
    const webTypeToInputTypeMap: Record<string, string> = {
      // 텍스트 관련
      text: "text",
      textarea: "text",
      email: "text",
      tel: "text",
      url: "text",
      password: "text",

      // 숫자 관련
      number: "number",
      decimal: "number",

      // 날짜 관련
      date: "date",
      datetime: "date",
      time: "date",

      // 선택 관련
      select: "select",
      dropdown: "select",
      checkbox: "checkbox",
      boolean: "checkbox",
      radio: "radio",

      // 참조 관련
      code: "code",
      entity: "entity",

      // 기타
      file: "text",
      button: "text",
    };

    return webTypeToInputTypeMap[webType] || "text";
  }

  /**
   * 권한 검증 (슈퍼관리자 확인)
   */
  private validateSuperAdminPermission(userCompanyCode: string): void {
    if (userCompanyCode !== "*") {
      throw new Error("최고 관리자 권한이 필요합니다.");
    }
  }

  /**
   * 테이블 존재 여부 확인
   */
  private async checkTableExists(tableName: string): Promise<boolean> {
    try {
      const result = await queryOne<{ exists: boolean }>(
        `
        SELECT EXISTS (
          SELECT FROM information_schema.tables 
          WHERE table_schema = 'public' 
          AND table_name = $1
        )
      `,
        [tableName]
      );

      return result?.exists || false;
    } catch (error) {
      logger.error("테이블 존재 확인 오류:", error);
      return false;
    }
  }

  /**
   * 컬럼 존재 여부 확인
   */
  private async checkColumnExists(
    tableName: string,
    columnName: string
  ): Promise<boolean> {
    try {
      const result = await queryOne<{ exists: boolean }>(
        `
        SELECT EXISTS (
          SELECT FROM information_schema.columns 
          WHERE table_schema = 'public' 
          AND table_name = $1 
          AND column_name = $2
        )
      `,
        [tableName, columnName]
      );

      return result?.exists || false;
    } catch (error) {
      logger.error("컬럼 존재 확인 오류:", error);
      return false;
    }
  }

  /**
   * 생성된 테이블 정보 조회
   */
  async getCreatedTableInfo(tableName: string): Promise<{
    tableInfo: any;
    columns: any[];
  } | null> {
    try {
      // 테이블 정보 조회
      const tableInfo = await queryOne(
        `SELECT * FROM table_labels WHERE table_name = $1`,
        [tableName]
      );

      // 컬럼 정보 조회 (table_type_columns에서)
      const columns = await query(
        `SELECT * FROM table_type_columns WHERE table_name = $1 AND company_code = '*' ORDER BY display_order ASC`,
        [tableName]
      );

      if (!tableInfo) {
        return null;
      }

      return {
        tableInfo,
        columns,
      };
    } catch (error) {
      logger.error("생성된 테이블 정보 조회 실패:", error);
      return null;
    }
  }

  /**
   * 테이블 삭제 (DROP TABLE)
   */
  async dropTable(
    tableName: string,
    userCompanyCode: string,
    userId: string
  ): Promise<DDLExecutionResult> {
    // DDL 실행 시작 로그
    await DDLAuditLogger.logDDLStart(
      userId,
      userCompanyCode,
      "DROP_TABLE",
      tableName,
      {}
    );

    try {
      // 1. 권한 검증 (최고 관리자만 가능)
      this.validateSuperAdminPermission(userCompanyCode);

      // 2. 테이블 존재 여부 확인
      const tableExists = await this.checkTableExists(tableName);
      if (!tableExists) {
        const errorMessage = `테이블 '${tableName}'이 존재하지 않습니다.`;

        await DDLAuditLogger.logDDLExecution(
          userId,
          userCompanyCode,
          "DROP_TABLE",
          tableName,
          "TABLE_NOT_FOUND",
          false,
          errorMessage
        );

        return {
          success: false,
          message: errorMessage,
          error: {
            code: "TABLE_NOT_FOUND",
            details: errorMessage,
          },
        };
      }

      // 3. DDL 쿼리 생성
      const ddlQuery = `DROP TABLE IF EXISTS "${tableName}" CASCADE`;

      // 4. 트랜잭션으로 안전하게 실행
      await transaction(async (client) => {
        // 4-1. 테이블 삭제
        await client.query(ddlQuery);

        // 4-2. 관련 메타데이터 삭제
        await client.query(`DELETE FROM table_type_columns WHERE table_name = $1`, [
          tableName,
        ]);
        await client.query(`DELETE FROM table_labels WHERE table_name = $1`, [
          tableName,
        ]);
      });

      // 5. 성공 로그 기록
      await DDLAuditLogger.logDDLExecution(
        userId,
        userCompanyCode,
        "DROP_TABLE",
        tableName,
        ddlQuery,
        true
      );

      logger.info("테이블 삭제 성공", {
        tableName,
        userId,
      });

      // 테이블 삭제 후 관련 캐시 무효화
      this.invalidateTableCache(tableName);

      return {
        success: true,
        message: `테이블 '${tableName}'이 성공적으로 삭제되었습니다.`,
        executedQuery: ddlQuery,
      };
    } catch (error) {
      const errorMessage = `테이블 삭제 실패: ${(error as Error).message}`;

      // 실패 로그 기록
      await DDLAuditLogger.logDDLExecution(
        userId,
        userCompanyCode,
        "DROP_TABLE",
        tableName,
        `FAILED: ${(error as Error).message}`,
        false,
        errorMessage
      );

      logger.error("테이블 삭제 실패:", {
        tableName,
        userId,
        error: (error as Error).message,
        stack: (error as Error).stack,
      });

      return {
        success: false,
        message: errorMessage,
        error: {
          code: "EXECUTION_FAILED",
          details: (error as Error).message,
        },
      };
    }
  }

  /**
   * 테이블 관련 캐시 무효화
   * DDL 작업 후 호출하여 캐시된 데이터를 클리어
   */
  private invalidateTableCache(tableName: string): void {
    try {
      // 테이블 컬럼 관련 캐시 무효화
      const columnCacheDeleted = cache.deleteByPattern(
        `table_columns:${tableName}`
      );
      const countCacheDeleted = cache.deleteByPattern(
        `table_column_count:${tableName}`
      );
      cache.delete("table_list");

      const totalDeleted = columnCacheDeleted + countCacheDeleted + 1;

      logger.info(
        `테이블 캐시 무효화 완료: ${tableName}, 삭제된 키: ${totalDeleted}개`
      );
    } catch (error) {
      logger.warn(`테이블 캐시 무효화 실패: ${tableName}`, error);
    }
  }
}
