/**
 * PostgreSQL Raw Query 기반 데이터베이스 매니저
 *
 * Prisma → Raw Query 전환의 핵심 모듈
 * - Connection Pool 기반 안정적인 연결 관리
 * - 트랜잭션 지원
 * - 타입 안전성 보장
 * - 자동 재연결 및 에러 핸들링
 */

import {
  Pool,
  PoolClient,
  QueryResult as PgQueryResult,
  QueryResultRow,
  types,
} from "pg";
import config from "../config/environment";

// DATE 타입(OID 1082)을 문자열로 반환 (타임존 변환에 의한 -1day 버그 방지)
types.setTypeParser(1082, (val: string) => val);

// PostgreSQL 연결 풀
let pool: Pool | null = null;

/**
 * 데이터베이스 연결 풀 초기화
 */
export const initializePool = (): Pool => {
  if (pool) {
    return pool;
  }

  // DATABASE_URL 파싱 (postgresql://user:password@host:port/database)
  const databaseUrl = config.databaseUrl;

  // URL 파싱 로직
  const dbConfig = parseDatabaseUrl(databaseUrl);

  pool = new Pool({
    host: dbConfig.host,
    port: dbConfig.port,
    database: dbConfig.database,
    user: dbConfig.user,
    password: dbConfig.password,

    // 연결 풀 설정
    min: config.nodeEnv === "production" ? 5 : 2,
    max: config.nodeEnv === "production" ? 20 : 10,

    // 타임아웃 설정
    connectionTimeoutMillis: 30000, // 30초
    idleTimeoutMillis: 600000, // 10분

    // 연결 유지 설정
    keepAlive: true,
    keepAliveInitialDelayMillis: 10000,

    // 쿼리 타임아웃
    statement_timeout: 60000, // 60초 (동적 테이블 생성 등 고려)
    query_timeout: 60000,

    // Application Name
    application_name: "WACE-PLM-Backend",
  });

  // 연결 풀 이벤트 핸들러
  pool.on("connect", (client) => {
    client.query("SET timezone = 'Asia/Seoul'");
    if (config.debug) {
      console.log("✅ PostgreSQL 클라이언트 연결 생성 (timezone: Asia/Seoul)");
    }
  });

  pool.on("acquire", (client) => {
    if (config.debug) {
      console.log("🔒 PostgreSQL 클라이언트 획득");
    }
  });

  pool.on("remove", (client) => {
    if (config.debug) {
      console.log("🗑️  PostgreSQL 클라이언트 제거");
    }
  });

  pool.on("error", (err, client) => {
    console.error("❌ PostgreSQL 연결 풀 에러:", err);
    // 연결 풀 에러 발생 시 자동 재연결 시도
    // Pool은 자동으로 연결을 재생성하므로 별도 처리 불필요
    // 다만, 연속 에러 발생 시 알림이 필요할 수 있음
  });

  // 연결 풀 상태 체크 (5분마다)
  setInterval(() => {
    if (pool) {
      const status = {
        totalCount: pool.totalCount,
        idleCount: pool.idleCount,
        waitingCount: pool.waitingCount,
      };
      // 대기 중인 연결이 많으면 경고
      if (status.waitingCount > 5) {
        console.warn("⚠️ PostgreSQL 연결 풀 대기열 증가:", status);
      }
    }
  }, 5 * 60 * 1000);

  console.log(
    `🚀 PostgreSQL 연결 풀 초기화 완료: ${dbConfig.host}:${dbConfig.port}/${dbConfig.database}`
  );

  return pool;
};

/**
 * DATABASE_URL 파싱 헬퍼 함수
 */
function parseDatabaseUrl(url: string) {
  // postgresql://user:password@host:port/database
  const regex = /postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/;
  const match = url.match(regex);

  if (!match) {
    // URL 파싱 실패 시 기본값 사용
    console.warn("⚠️  DATABASE_URL 파싱 실패, 기본값 사용");
    return {
      host: "localhost",
      port: 5432,
      database: "ilshin",
      user: "postgres",
      password: "postgres",
    };
  }

  return {
    user: decodeURIComponent(match[1]),
    password: decodeURIComponent(match[2]),
    host: match[3],
    port: parseInt(match[4], 10),
    database: match[5],
  };
}

/**
 * 연결 풀 가져오기
 */
export const getPool = (): Pool => {
  if (!pool) {
    return initializePool();
  }
  return pool;
};

/**
 * 기본 쿼리 실행 함수
 *
 * @param text SQL 쿼리 문자열 (Parameterized Query)
 * @param params 쿼리 파라미터 배열
 * @returns 쿼리 결과 배열
 *
 * @example
 * const users = await query<User>('SELECT * FROM users WHERE user_id = $1', ['user123']);
 */
export async function query<T extends QueryResultRow = any>(
  text: string,
  params?: any[]
): Promise<T[]> {
  const pool = getPool();
  const client = await pool.connect();

  try {
    const startTime = Date.now();
    const result: PgQueryResult<T> = await client.query(text, params);
    const duration = Date.now() - startTime;

    if (config.debug) {
      console.log("🔍 쿼리 실행:", {
        query: text,
        params,
        rowCount: result.rowCount,
        duration: `${duration}ms`,
      });
    }

    return result.rows;
  } catch (error: any) {
    console.error("❌ 쿼리 실행 실패:", {
      query: text,
      params,
      error: error.message,
    });
    throw error;
  } finally {
    client.release();
  }
}

/**
 * 단일 행 조회 쿼리 (결과가 없으면 null 반환)
 *
 * @param text SQL 쿼리 문자열
 * @param params 쿼리 파라미터
 * @returns 단일 행 또는 null
 *
 * @example
 * const user = await queryOne<User>('SELECT * FROM users WHERE user_id = $1', ['user123']);
 */
export async function queryOne<T extends QueryResultRow = any>(
  text: string,
  params?: any[]
): Promise<T | null> {
  const rows = await query<T>(text, params);
  return rows.length > 0 ? rows[0] : null;
}

/**
 * 트랜잭션 실행 함수
 *
 * @param callback 트랜잭션 내에서 실행할 함수
 * @returns 콜백 함수의 반환값
 *
 * @example
 * const result = await transaction(async (client) => {
 *   await client.query('INSERT INTO users (...) VALUES (...)', []);
 *   await client.query('INSERT INTO user_roles (...) VALUES (...)', []);
 *   return { success: true };
 * });
 */
export async function transaction<T>(
  callback: (client: PoolClient) => Promise<T>
): Promise<T> {
  const pool = getPool();
  const client = await pool.connect();

  try {
    await client.query("BEGIN");

    if (config.debug) {
      console.log("🔄 트랜잭션 시작");
    }

    const result = await callback(client);

    await client.query("COMMIT");

    if (config.debug) {
      console.log("✅ 트랜잭션 커밋 완료");
    }

    return result;
  } catch (error: any) {
    await client.query("ROLLBACK");

    console.error("❌ 트랜잭션 롤백:", error.message);
    throw error;
  } finally {
    client.release();
  }
}

/**
 * 연결 풀 종료 (앱 종료 시 호출)
 */
export async function closePool(): Promise<void> {
  if (pool) {
    await pool.end();
    pool = null;
    console.log("🛑 PostgreSQL 연결 풀 종료");
  }
}

/**
 * 연결 풀 상태 확인
 */
export function getPoolStatus() {
  const pool = getPool();
  return {
    totalCount: pool.totalCount,
    idleCount: pool.idleCount,
    waitingCount: pool.waitingCount,
  };
}

// Pool 직접 접근 (필요한 경우)
export { pool };

// 기본 익스포트 (편의성)
export default {
  query,
  queryOne,
  transaction,
  getPool,
  initializePool,
  closePool,
  getPoolStatus,
};
