/**
 * Amaranth(Wehago) 전자결재 API 클라이언트 — Node 포팅
 *
 *  - 자바 AmaranthApprovalApiClient.java 1:1 포팅
 *  - 인증 정보(callerName/accessToken/hashKey/groupSeq/aesKey/baseUrl)는
 *    DB 의 external_rest_api_connections 테이블에서 connection_name='Amaranth - 결재'
 *    레코드를 조회해 사용 (하드코딩 제거)
 *  - HMAC-SHA256 wehago-sign / AES-128-CBC empSeq 암호화 동일하게 처리
 *
 *  Endpoints (호출자가 path 만 지정):
 *    /apiproxy/api99u01A01            — 사용자 인증 토큰 발급 (서버 인증 + empSeqEnc)
 *    /apiproxy/api99u02A01            — 결재함 조회
 *    /apiproxy/api99u02A02            — 결재 문서 목록
 *    /apiproxy/api99u02A04            — 결재 문서 상세
 *    /apiproxy/authUser/api99u01A02   — 결재 작성 SSO URL (사용자 인증)
 *    /apiproxy/authUser/api99u03A03   — 결재 문서 상신 (사용자 인증)
 */
import crypto from "crypto";
import https from "https";
import { query, queryOne } from "../database/db";
import { logger } from "../utils/logger";

const CONNECTION_NAME = "Amaranth - 결재";

// external_rest_api_connections.auth_config 의 accessToken/hashKey 는
// ExternalRestApiConnectionService 에서 AES-256-GCM 으로 암호화되어 저장됨
// (`iv:authTag:ciphertext` 3-part hex). 같은 키/알고리즘으로 복호화한다.
const ENC_ALGORITHM = "aes-256-gcm";
const ENC_KEY = crypto.scryptSync(
  process.env.DB_PASSWORD_SECRET || "default-secret-key-change-in-production",
  "salt",
  32,
);

function tryDecrypt(value: string): string {
  if (!value || typeof value !== "string") return value;
  const parts = value.split(":");
  if (parts.length !== 3) return value; // 평문 그대로 (마이그레이션 호환)
  try {
    const iv = Buffer.from(parts[0], "hex");
    const authTag = Buffer.from(parts[1], "hex");
    const decipher = crypto.createDecipheriv(ENC_ALGORITHM, ENC_KEY, iv) as crypto.DecipherGCM;
    decipher.setAuthTag(authTag);
    let out = decipher.update(parts[2], "hex", "utf8");
    out += decipher.final("utf8");
    return out;
  } catch {
    return value; // 복호화 실패 시 원본 (평문 가정)
  }
}

interface AmaranthAuth {
  baseUrl: string;
  callerName: string;
  accessToken: string;
  hashKey: string;
  groupSeq: string;
  aesKey: string;
}

/** DB 의 외부 커넥션 'Amaranth - 결재' 에서 인증 정보를 로드 (민감 필드 자동 복호화) */
export async function loadApprovalConnection(): Promise<AmaranthAuth> {
  const row = await queryOne<any>(
    `SELECT base_url, auth_config FROM external_rest_api_connections
       WHERE connection_name = $1 AND COALESCE(is_active, 'Y') = 'Y'
       LIMIT 1`,
    [CONNECTION_NAME],
  );
  if (!row) {
    throw new Error(`외부 커넥션 '${CONNECTION_NAME}' 가 등록되어 있지 않습니다.`);
  }
  const cfg = typeof row.auth_config === "string" ? JSON.parse(row.auth_config) : row.auth_config || {};
  if (!cfg.callerName || !cfg.accessToken || !cfg.hashKey || !cfg.groupSeq) {
    throw new Error(`'${CONNECTION_NAME}' auth_config 에 callerName/accessToken/hashKey/groupSeq 가 모두 필요합니다.`);
  }
  return {
    baseUrl: String(row.base_url || "").replace(/\/+$/, ""),
    callerName: cfg.callerName, // 평문 (암호화 대상 아님)
    accessToken: tryDecrypt(cfg.accessToken),
    hashKey: tryDecrypt(cfg.hashKey),
    groupSeq: cfg.groupSeq, // 평문
    aesKey: cfg.aesKey || "8441e27489d402cd",
  };
}

/** HMAC-SHA256 wehago-sign : Base64( HMAC(authToken+transactionId+timestamp+urlPath, hashKey) ) */
function generateWehagoSign(authToken: string, transactionId: string, timestamp: string, urlPath: string, hashKey: string): string {
  const value = authToken + transactionId + timestamp + urlPath;
  return crypto.createHmac("sha256", hashKey).update(value, "utf8").digest("base64");
}

/** 32자리 hex transaction-id */
function generateTransactionId(): string {
  return crypto.randomBytes(16).toString("hex");
}

/**
 * AES-128-CBC PKCS5Padding 암호화 (empSeq)
 *   평문: yyyyMMddHHmmss(KST) + ▦(▦) + value
 *   key = aesKey 앞 16바이트, IV = key 동일
 */
function encryptEmpSeq(value: string, aesKey: string): string {
  // 한국시간(KST) yyyyMMddHHmmss
  const now = new Date();
  const kst = new Date(now.getTime() + (9 * 60 - now.getTimezoneOffset()) * 60_000);
  const pad = (n: number, w = 2) => String(n).padStart(w, "0");
  const ts = `${kst.getUTCFullYear()}${pad(kst.getUTCMonth() + 1)}${pad(kst.getUTCDate())}${pad(kst.getUTCHours())}${pad(kst.getUTCMinutes())}${pad(kst.getUTCSeconds())}`;
  const plain = `${ts}▦${value}`;

  const keyBuf = Buffer.alloc(16);
  Buffer.from(aesKey, "utf8").copy(keyBuf, 0, 0, 16);
  const cipher = crypto.createCipheriv("aes-128-cbc", keyBuf, keyBuf);
  const encrypted = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
  return encrypted.toString("base64");
}

/** Wehago 공통 헤더 (서버 인증 / 사용자 인증 공용) */
function buildHeaders(opts: {
  conn: AmaranthAuth;
  authToken: string; // 서버 인증 = accessToken, 사용자 인증 = 발급받은 authToken
  hashKey: string;   // 서버 = conn.hashKey, 사용자 = 발급받은 hashKey
  urlPath: string;
}): Record<string, string> {
  const { conn, authToken, hashKey, urlPath } = opts;
  const transactionId = generateTransactionId();
  const timestamp = String(Math.floor(Date.now() / 1000));
  return {
    "Content-Type": "application/json; charset=UTF-8",
    Accept: "application/json",
    callerName: conn.callerName,
    Authorization: `Bearer ${authToken}`,
    "transaction-id": transactionId,
    timestamp,
    groupSeq: conn.groupSeq,
    "wehago-sign": generateWehagoSign(authToken, transactionId, timestamp, urlPath, hashKey),
  };
}

/** HTTP POST 요청 (Node 내장 https — fetch 의존 X) */
function postJson(fullUrl: string, headers: Record<string, string>, body: string): Promise<{ status: number; body: string }> {
  return new Promise((resolve, reject) => {
    const u = new URL(fullUrl);
    const req = https.request(
      {
        method: "POST",
        host: u.hostname,
        port: u.port || 443,
        path: u.pathname + (u.search || ""),
        headers: { ...headers, "Content-Length": Buffer.byteLength(body, "utf8") },
        // 자바 원본은 SSL 검증 우회 — 운영 인증서 문제 시 동일하게 처리
        rejectUnauthorized: false,
      },
      (res) => {
        const chunks: Buffer[] = [];
        res.on("data", (c) => chunks.push(c));
        res.on("end", () => resolve({ status: res.statusCode || 0, body: Buffer.concat(chunks).toString("utf8") }));
      },
    );
    req.on("error", reject);
    req.write(body);
    req.end();
  });
}

// ─────────────────────────────────────────────────────────────
// Public API — 자바 AmaranthApprovalApiClient 의 메서드 1:1 매핑
// ─────────────────────────────────────────────────────────────

/**
 * 1) 인증 토큰 발급 (api99u01A01)
 *   - 서버 인증 헤더(accessToken/hashKey) + body 에 empSeqEnc(AES)
 *   - 응답: { resultCode, authToken, hashKey, ... }
 */
export async function getAuthToken(empSeq: string): Promise<{ success: boolean; authToken?: string; hashKey?: string; resultMsg?: string }> {
  const conn = await loadApprovalConnection();
  const urlPath = "/apiproxy/api99u01A01";
  const empSeqEnc = encryptEmpSeq(empSeq, conn.aesKey);
  const body = JSON.stringify({
    header: {},
    body: { groupSeq: conn.groupSeq, empSeqEnc },
  });
  const headers = buildHeaders({ conn, authToken: conn.accessToken, hashKey: conn.hashKey, urlPath });
  const res = await postJson(conn.baseUrl + urlPath, headers, body);
  try {
    const j = JSON.parse(res.body);
    if (String(j.resultCode) === "0") {
      return { success: true, authToken: j.authToken, hashKey: j.hashKey };
    }
    return { success: false, resultMsg: j.resultMsg || `HTTP ${res.status}` };
  } catch {
    return { success: false, resultMsg: `parse error: ${res.body.slice(0, 200)}` };
  }
}

/** 2) 결재함 조회 (api99u02A01) — 서버 인증 */
export async function getApprovalBoxList(empSeq: string, compSeq: string, deptSeq?: string): Promise<any> {
  const conn = await loadApprovalConnection();
  const urlPath = "/apiproxy/api99u02A01";
  const body = JSON.stringify({
    header: { empSeq, groupSeq: conn.groupSeq },
    body: {
      companyInfo: deptSeq ? { compSeq, deptSeq } : { compSeq },
    },
  });
  const headers = buildHeaders({ conn, authToken: conn.accessToken, hashKey: conn.hashKey, urlPath });
  const res = await postJson(conn.baseUrl + urlPath, headers, body);
  return safeJson(res.body);
}

/** 3) 결재 문서 목록 (api99u02A02) — 서버 인증 */
export async function getApprovalDocList(args: {
  empSeq: string;
  compSeq: string;
  menuId: string; // 1001000 = 미결 등
  fromDt?: string;
  toDt?: string;
  keyWord?: string;
  page?: string;
  pageSize?: string;
}): Promise<any> {
  const conn = await loadApprovalConnection();
  const urlPath = "/apiproxy/api99u02A02";
  const body = JSON.stringify({
    header: { empSeq: args.empSeq, groupSeq: conn.groupSeq },
    body: {
      menuId: args.menuId,
      ...(args.fromDt ? { fromDt: args.fromDt } : {}),
      ...(args.toDt ? { toDt: args.toDt } : {}),
      pageSize: args.pageSize || "30",
      keyWord: args.keyWord || "",
      page: args.page || "1",
      sort: "10",
      langCode: "kr",
      searchKind: "1",
      companyInfo: { compSeq: args.compSeq },
    },
  });
  const headers = buildHeaders({ conn, authToken: conn.accessToken, hashKey: conn.hashKey, urlPath });
  const res = await postJson(conn.baseUrl + urlPath, headers, body);
  return safeJson(res.body);
}

/** 4) 결재 문서 상세 (api99u02A04) — 서버 인증 */
export async function getApprovalDocDetail(args: {
  empSeq: string;
  compSeq: string;
  deptSeq: string;
  docId: string;
}): Promise<any> {
  const conn = await loadApprovalConnection();
  const urlPath = "/apiproxy/api99u02A04";
  const body = JSON.stringify({
    header: { pId: "", tId: "", groupSeq: conn.groupSeq, empSeq: args.empSeq },
    body: {
      migYn: "0",
      langCode: "kr",
      spMigYn: "0",
      docId: args.docId,
      spDocId: "",
      companyInfo: { compSeq: args.compSeq, deptSeq: args.deptSeq },
    },
  });
  const headers = buildHeaders({ conn, authToken: conn.accessToken, hashKey: conn.hashKey, urlPath });
  const res = await postJson(conn.baseUrl + urlPath, headers, body);
  return safeJson(res.body);
}

/**
 * 5) 결재 작성 SSO URL (authUser/api99u01A02) — 사용자 인증
 *   1) getAuthToken(empSeq) 으로 사용자 토큰 발급
 *   2) 발급 토큰으로 SSO 요청 → fullUrl 응답
 */
export async function getSsoUrl(args: {
  empSeq: string;
  outProcessCode?: string;
  formId?: string;
  approKey: string;
  subjectStr?: string;
  mod: "W" | "V" | "D";
  compSeq?: string;
  deptSeq?: string;
  loginId?: string;
  fileList?: string;
}): Promise<any> {
  const auth = await getAuthToken(args.empSeq);
  if (!auth.success || !auth.authToken || !auth.hashKey) {
    throw new Error(`인증 토큰 발급 실패: ${auth.resultMsg}`);
  }
  const conn = await loadApprovalConnection();
  const urlPath = "/apiproxy/authUser/api99u01A02";
  const empSeqEnc = encryptEmpSeq(args.empSeq, conn.aesKey);

  const appParams: Record<string, any> = {
    approKey: args.approKey,
    mod: args.mod,
    ...(args.loginId ? { loginId: args.loginId } : {}),
    ...(args.outProcessCode ? { outProcessCode: args.outProcessCode } : {}),
    ...(args.formId ? { formId: args.formId } : {}),
    ...(args.subjectStr ? { subjectStr: args.subjectStr } : {}),
    ...(args.fileList ? { fileList: args.fileList } : {}),
    contentsEnc: "U",
  };
  const body = JSON.stringify({
    header: {},
    body: {
      groupSeq: conn.groupSeq,
      empSeqEnc,
      type: "popup",
      popupCode: "UBAP036",
      ...(args.compSeq ? { companyInfo: { compSeq: args.compSeq, ...(args.deptSeq ? { deptSeq: args.deptSeq } : {}) } } : {}),
      appParams,
    },
  });
  const headers = buildHeaders({ conn, authToken: auth.authToken, hashKey: auth.hashKey, urlPath });
  const res = await postJson(conn.baseUrl + urlPath, headers, body);
  return safeJson(res.body);
}

/** 6) 결재 문서 상신 (authUser/api99u03A03) — 사용자 인증, requestBody 통째로 전달 */
export async function submitApprovalDoc(empSeq: string, requestBody: object | string): Promise<any> {
  const auth = await getAuthToken(empSeq);
  if (!auth.success || !auth.authToken || !auth.hashKey) {
    throw new Error(`인증 토큰 발급 실패: ${auth.resultMsg}`);
  }
  const conn = await loadApprovalConnection();
  const urlPath = "/apiproxy/authUser/api99u03A03";
  const body = typeof requestBody === "string" ? requestBody : JSON.stringify(requestBody);
  const headers = buildHeaders({ conn, authToken: auth.authToken, hashKey: auth.hashKey, urlPath });
  const res = await postJson(conn.baseUrl + urlPath, headers, body);
  return safeJson(res.body);
}

function safeJson(text: string): any {
  try {
    return JSON.parse(text);
  } catch {
    logger.warn(`[amaranthApproval] JSON 파싱 실패: ${text.slice(0, 200)}`);
    return { rawResponse: text };
  }
}
