import { S3Service } from '@lib/s3/src';
import { Injectable } from '@nestjs/common';
import fontkit from '@pdf-lib/fontkit';
import dayjs from 'dayjs';
import { PDFDocument, rgb } from 'pdf-lib';

//* 품목 라인 — 상품명+단가별 분리(동일 상품이라도 단가가 다르면 별도 행)
export interface AgencySettlementStatementLineItem {
  productName: string | null;
  /** 단가 (VAT 포함, ord_settlement_price) */
  unitPrice: number;
  /** 주문수 */
  orderCount: number;
  /** 금액 (단가 × 주문수, VAT 포함) */
  totalPrice: number;
}

//* 구매내역서 렌더링 데이터
export interface AgencySettlementStatementData {
  /** 정산코드 */
  astCode: string;
  /** 정산연월 (YYYY-MM) */
  astDate: string;
  /** 정산확정일시 */
  astConfirmedAt?: string | null;
  /** 정산확정금액 (INCLUDE 스냅샷, VAT 포함) */
  astConfirmedPrice: number;
  /** 정산 반영(INCLUDE) 건수 */
  includeCount: number;
  /** 품목 라인 (INCLUDE 주문의 상품명+단가별 집계) */
  lineItems: AgencySettlementStatementLineItem[];
  /** 공급받는자(기업) */
  agency: {
    agcCorporateName: string;
    agcRegistrationNumber?: string | null;
    agcRepresentativeName?: string | null;
  };
}

//* 공급자(자사) 정보 — insuniverse-site front/src/shared/ui/Footer.tsx 기준 (법인 정보 변경 시 함께 갱신)
const SUPPLIER = {
  name: '주식회사 인파스',
  registrationNumber: '601-86-03236',
  representative: '조창훈',
};

const KOREAN_DIGITS = ['', '일', '이', '삼', '사', '오', '육', '칠', '팔', '구'];
const KOREAN_SMALL_UNITS = ['', '십', '백', '천'];
const KOREAN_BIG_UNITS = ['', '만', '억', '조'];

/** 금액 한글 표기 — 6,952,000 → '육백구십오만이천' (합계 문구 "일금 ...원정"용) */
const toKoreanAmount = (value: number): string => {
  const amount = Math.floor(Math.abs(value));
  if (amount === 0) return '영';

  let rest = amount;
  let result = '';
  let bigIdx = 0;
  while (rest > 0) {
    const chunk = rest % 10000;
    if (chunk > 0) {
      let chunkStr = '';
      let c = chunk;
      for (let i = 0; c > 0; i++) {
        const digit = c % 10;
        if (digit > 0) {
          chunkStr = KOREAN_DIGITS[digit] + KOREAN_SMALL_UNITS[i] + chunkStr;
        }
        c = Math.floor(c / 10);
      }
      result = chunkStr + KOREAN_BIG_UNITS[bigIdx] + result;
    }
    rest = Math.floor(rest / 10000);
    bigIdx++;
  }
  return result;
};

/**
 * 구매내역서 PDF 빌더 (A4 · 기업 결재 제출용 요약 증빙)
 *  - 레이아웃은 planning/mockup/후불정산관리_구매내역서.html(admin) 기준:
 *    제목+정산번호/발행일 → 공급자/공급받는자 박스 → 정산연월·반영건수 →
 *    품목 테이블(품목/주문수/단가/공급가액/세액/금액, 상품·단가별 행 분리) →
 *    합계(공급가액/세액/합계) → 한글 금액 문구 → 하단 확인 문구.
 *  - 금액은 정산확정금액(ast_confirmed_price, 반영분) 기준 — 미반영·이월 제외.
 *  - 단가 VAT 포함 정책: 공급가액 = 금액/1.1 반올림, 세액(VAT) = 금액 - 공급가액.
 *  - 한글 렌더링을 위해 인보이스와 동일한 S3 폰트(fonts/GmarketSansMedium.otf) 사용.
 */
@Injectable()
export class AgencySettlementStatementBuilder {
  //* A4 (pt)
  private static readonly PAGE_WIDTH = 595.28;
  private static readonly PAGE_HEIGHT = 841.89;
  private static readonly MARGIN = 56;

  constructor(private s3Service: S3Service) {}

  //* 폰트 바이트 메모이즈 — PDF 생성마다 S3 재다운로드 방지.
  //*   Promise 자체를 캐시해 동시 호출도 단일 다운로드로 수렴, 실패 시 캐시 비워 다음 호출에서 재시도.
  //*   (invoice 모듈 InvoiceStorageService.fetchFont 재사용은 도메인 간 모듈 의존이 생겨 로컬 메모이즈 선택)
  private fontBytesPromise?: Promise<Uint8Array>;

  private fetchFont(): Promise<Uint8Array> {
    this.fontBytesPromise ??= this.s3Service
      .getObject('fonts/GmarketSansMedium.otf')
      .then((file) => file.transformToByteArray())
      .catch((err) => {
        this.fontBytesPromise = undefined;
        throw err;
      });
    return this.fontBytesPromise;
  }

  async build(data: AgencySettlementStatementData): Promise<Buffer> {
    const doc = await PDFDocument.create();
    doc.registerFontkit(fontkit);
    const font = await doc.embedFont(await this.fetchFont());

    const margin = AgencySettlementStatementBuilder.MARGIN;
    const width = AgencySettlementStatementBuilder.PAGE_WIDTH;
    const height = AgencySettlementStatementBuilder.PAGE_HEIGHT;
    const right = width - margin;

    const gray = rgb(0.45, 0.45, 0.45);
    const black = rgb(0.15, 0.15, 0.15);
    const line = rgb(0.78, 0.78, 0.78);
    const lineLight = rgb(0.9, 0.91, 0.93);
    const headerBg = rgb(0.165, 0.18, 0.247); //* 목업 품목 헤더색 #2a2e3f
    const boxBg = rgb(0.968, 0.972, 0.984); //* 목업 합계 문구 배경 #f7f8fb

    const money = (value: number) => `${value.toLocaleString('ko-KR')}`;
    const textWidth = (text: string, size: number) =>
      font.widthOfTextAtSize(text, size);

    let page = doc.addPage([width, height]);
    const drawRight = (
      text: string,
      x: number,
      y: number,
      size: number,
      color = black,
    ) => page.drawText(text, { x: x - textWidth(text, size), y, size, font, color });
    const drawLeft = (
      text: string,
      x: number,
      y: number,
      size: number,
      color = black,
    ) => page.drawText(text, { x, y, size, font, color });
    const drawLine = (y: number, thickness = 0.7, color = line) =>
      page.drawLine({
        start: { x: margin, y },
        end: { x: right, y },
        thickness,
        color,
      });

    //* ===== 문서 헤더 — 제목(좌) + 정산번호·발행일(우) =====
    drawLeft('구 매 내 역 서', margin, 760, 22);
    drawRight(`정산번호  ${data.astCode}`, right, 772, 9.5, gray);
    drawRight(`발행일  ${dayjs().format('YYYY-MM-DD')}`, right, 758, 9.5, gray);
    drawLine(748, 2, black);

    //* ===== 공급자 / 공급받는자 박스 =====
    const partyTop = 736;
    const partyHeight = 86;
    const partyBottom = partyTop - partyHeight;
    const centerX = width / 2;
    page.drawRectangle({
      x: margin,
      y: partyBottom,
      width: right - margin,
      height: partyHeight,
      borderWidth: 0.8,
      borderColor: line,
    });
    page.drawLine({
      start: { x: centerX, y: partyTop },
      end: { x: centerX, y: partyBottom },
      thickness: 0.8,
      color: line,
    });

    const drawParty = (
      x: number,
      cap: string,
      rows: [string, string][],
    ) => {
      let py = partyTop - 20;
      drawLeft(cap, x, py, 8.5, gray);
      py -= 18;
      for (const [label, value] of rows) {
        drawLeft(label, x, py, 9.5, gray);
        drawLeft(value, x + 44, py, 9.5);
        py -= 17;
      }
    };
    drawParty(margin + 16, '공급자', [
      ['상호', SUPPLIER.name],
      ['사업자', SUPPLIER.registrationNumber || '-'],
      ['대표', SUPPLIER.representative || '-'],
    ]);
    drawParty(centerX + 16, '공급받는자', [
      ['상호', data.agency.agcCorporateName || '-'],
      ['사업자', data.agency.agcRegistrationNumber || '-'],
      ['대표', data.agency.agcRepresentativeName || '-'],
    ]);

    //* ===== 메타 — 정산연월 · 정산 반영 건수 =====
    let y = partyBottom - 24;
    {
      let metaX = margin;
      const drawMeta = (label: string, value: string) => {
        drawLeft(label, metaX, y, 9.5, gray);
        metaX += textWidth(label, 9.5) + 5;
        drawLeft(value, metaX, y, 9.5);
        metaX += textWidth(value, 9.5) + 22;
      };
      drawMeta('정산연월', data.astDate);
      drawMeta('정산 반영', `${money(data.includeCount)}건`);
    }

    //* ===== 품목 테이블 — 품목 / 주문수 / 단가 / 공급가액 / 세액(VAT) / 금액 =====
    const colItemX = margin + 8;
    const colCountX = margin + 212;
    const colUnitX = margin + 272;
    const colSupplyX = margin + 348;
    const colVatX = margin + 414;
    const colAmountX = right - 8;
    const rowHeight = 20;

    const drawTableHeader = () => {
      page.drawRectangle({
        x: margin,
        y: y - 6,
        width: right - margin,
        height: 22,
        color: headerBg,
      });
      const headerY = y;
      const white = rgb(1, 1, 1);
      drawLeft('품목', colItemX, headerY, 9, white);
      drawRight('주문수', colCountX, headerY, 9, white);
      drawRight('단가', colUnitX, headerY, 9, white);
      drawRight('공급가액', colSupplyX, headerY, 9, white);
      drawRight('세액(VAT)', colVatX, headerY, 9, white);
      drawRight('금액', colAmountX, headerY, 9, white);
      y -= rowHeight + 4;
    };

    y -= 26;
    drawTableHeader();

    for (const item of data.lineItems) {
      //* 페이지 넘침 가드 — 합계·문구 영역(하단 ~200pt) 확보 못 하면 다음 장에 헤더부터 재개
      if (y < 210) {
        page = doc.addPage([width, height]);
        y = height - margin - 20;
        drawTableHeader();
      }
      const supplyPrice = Math.round(item.totalPrice / 1.1);
      const vat = item.totalPrice - supplyPrice;
      const itemName = `${item.productName ?? '-'} 구독 (후불)`;
      drawLeft(itemName, colItemX, y, 9.5);
      drawRight(money(item.orderCount), colCountX, y, 9.5);
      drawRight(money(item.unitPrice), colUnitX, y, 9.5);
      drawRight(money(supplyPrice), colSupplyX, y, 9.5);
      drawRight(money(vat), colVatX, y, 9.5);
      drawRight(money(item.totalPrice), colAmountX, y, 9.5);
      y -= 7;
      drawLine(y, 0.6, lineLight);
      y -= rowHeight - 7;
    }
    if (data.lineItems.length === 0) {
      drawLeft('반영된 주문이 없습니다', colItemX, y, 9.5, gray);
      y -= 7;
      drawLine(y, 0.6, lineLight);
      y -= rowHeight - 7;
    }

    //* ===== 합계 — 공급가액 / 세액(VAT 10%) / 합계 금액 (확정 스냅샷 기준) =====
    const totalPrice = data.astConfirmedPrice ?? 0;
    const supplyTotal = Math.round(totalPrice / 1.1);
    const vatTotal = totalPrice - supplyTotal;
    const totalsLabelX = colVatX - 20;

    y -= 14;
    drawRight('공급가액', totalsLabelX, y, 9.5, gray);
    drawRight(money(supplyTotal), colAmountX, y, 9.5);
    y -= 17;
    drawRight('세액(VAT 10%)', totalsLabelX, y, 9.5, gray);
    drawRight(money(vatTotal), colAmountX, y, 9.5);
    y -= 10;
    page.drawLine({
      start: { x: totalsLabelX - textWidth('세액(VAT 10%)', 9.5) - 12, y },
      end: { x: right, y },
      thickness: 1.2,
      color: black,
    });
    y -= 18;
    drawRight('합계 금액', totalsLabelX, y, 11.5);
    drawRight(`${money(totalPrice)} 원`, colAmountX, y, 11.5);

    //* ===== 한글 금액 문구 =====
    y -= 34;
    page.drawRectangle({
      x: margin,
      y: y - 9,
      width: right - margin,
      height: 27,
      color: boxBg,
      borderWidth: 0.7,
      borderColor: lineLight,
    });
    drawLeft('합계 금액:', margin + 12, y, 9.5, gray);
    drawLeft(
      //* 통화기호(₩)는 폰트 글리프 미보유 시 렌더 실패 위험이 있어 '원' 표기로 대체
      `일금 ${toKoreanAmount(totalPrice)}원정 (${money(totalPrice)}원)`,
      margin + 12 + textWidth('합계 금액:', 9.5) + 6,
      y,
      9.5,
    );

    //* ===== 하단 확인 문구 =====
    y -= 40;
    page.drawLine({
      start: { x: margin, y: y + 14 },
      end: { x: right, y: y + 14 },
      thickness: 0.7,
      color: line,
      dashArray: [3, 3],
    });
    drawLeft('위와 같이 구매내역을 확인합니다.', margin, y - 2, 8.5, gray);
    drawLeft(
      '본 내역서는 정산확정금액(반영분) 기준으로 발행되었습니다.',
      margin,
      y - 16,
      8.5,
      gray,
    );
    const contactLine = data.astConfirmedAt
      ? `정산확정일 ${dayjs(data.astConfirmedAt).format('YYYY-MM-DD')} · 문의: 보험유니버스 정산팀`
      : '문의: 보험유니버스 정산팀';
    drawLeft(contactLine, margin, y - 30, 8.5, gray);

    const bytes = await doc.save();
    return Buffer.from(bytes);
  }
}
