'use server';

import { revalidatePath } from 'next/cache';
import { redirect, unstable_rethrow } from 'next/navigation';
import { verifySession } from '@/lib/auth/dal';
import { BackendRequestError } from '@/lib/http/backend-fetch';
import { fetchCommonCodes } from '@/lib/data/repositories/common-code-repository';
import {
  buildFileImageUrl,
  uploadAttachment,
  uploadAttachments,
} from '@/lib/data/repositories/file-repository';
import {
  createMerchantDetail,
  deleteMerchantDetail,
  fetchMerchantDetail,
  updateMerchantDetail,
} from '@/lib/data/repositories/merchant-detail-repository';
import {
  createMerchantProduct,
  deleteMerchantProduct,
  fetchMerchantProduct,
  updateMerchantProduct,
} from '@/lib/data/repositories/merchant-product-repository';
import { CODE_GROUP } from '@/lib/domain/common-code';
import {
  FILE_MODULE,
  MAX_ATTACHMENT_BYTES,
  allowedExtensions,
  isAllowedFileExtension,
} from '@/lib/domain/file-module';
import { MAX_EXTRA_IMAGES, MAX_IMAGE_BYTES } from '@/lib/domain/merchant-detail';
import {
  validateMerchantDetail,
  type MerchantDetailFormState,
  type MerchantDetailValues,
} from '@/lib/domain/merchant-detail-form';
import {
  MERCHANT_DETAILS_PATH,
  merchantDetailPath,
} from '@/lib/domain/merchant-detail-query';
import {
  MERCHANT_PRODUCT_BADGE_OPTIONS,
  type MerchantProductBadge,
} from '@/lib/domain/merchant-product';
import {
  validateMerchantProduct,
  type MerchantProductFormState,
} from '@/lib/domain/merchant-product-form';

/**
 * 가맹몰 상세 Server Action — 상세(가맹점당 1건)와 메뉴(여러 건) 둘 다 여기서 다룬다.
 *
 * 이슈: 목록의 [비활성]/[노출]은 백엔드가 상세 전체를 돌려줘야 안전하다. `PUT`이 모든 컬럼을
 *   무조건 덮어써서, 단건 조회가 비어 있는 지금 값 없이 보내면 소개·영업시간·이미지가 지워진다.
 *   그래서 조회 결과에 대표이미지가 없으면 목록 토글을 거부하고 수정 화면으로 안내한다.
 *   단건 SELECT가 보강되면(보고함) 그대로 동작한다.
 */

const SAVE_FAILED_MESSAGE = '저장하지 못했습니다. 잠시 후 다시 시도해 주세요.';
const DELETE_FAILED_MESSAGE = '삭제하지 못했습니다. 잠시 후 다시 시도해 주세요.';
const INVALID_REQUEST_MESSAGE = '요청이 올바르지 않습니다.';
const DETAIL_NOT_LOADED_MESSAGE =
  '백엔드가 상세 값을 돌려주지 않아 목록에서는 바꿀 수 없습니다. 수정 화면에서 바꿔 주세요.';
const IMAGE_OVERSIZE_MESSAGE = `이미지는 장당 ${Math.floor(MAX_IMAGE_BYTES / (1024 * 1024))}MB까지 올릴 수 있습니다.`;
const IMAGES_TOO_MANY_MESSAGE = `추가 이미지는 최대 ${MAX_EXTRA_IMAGES}장까지입니다.`;
const REQUEST_OVERSIZE_MESSAGE = `한 번에 올리는 이미지의 합이 ${Math.floor(MAX_ATTACHMENT_BYTES / (1024 * 1024))}MB를 넘습니다. 나눠서 저장해 주세요.`;

class InputError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'InputError';
  }
}

export type MerchantDetailActionResult =
  | { status: 'success' }
  | { status: 'error'; message: string };

function readString(formData: FormData, key: string): string {
  const value = formData.get(key);
  return typeof value === 'string' ? value : '';
}

function readOptionalInteger(formData: FormData, key: string): number | null {
  const raw = readString(formData, key).trim();
  if (raw === '') return null;
  const parsed = Number(raw);
  return Number.isFinite(parsed) ? parsed : Number.NaN;
}

function readFiles(formData: FormData, key: string): Array<File | null> {
  return formData
    .getAll(key)
    .map((entry) => (entry instanceof File && entry.size > 0 ? entry : null));
}

function assertImage(file: File): void {
  if (file.size > MAX_IMAGE_BYTES) {
    throw new InputError(IMAGE_OVERSIZE_MESSAGE);
  }
  if (!isAllowedFileExtension(FILE_MODULE.merchantImage, file.name)) {
    throw new InputError(
      `이미지는 ${allowedExtensions(FILE_MODULE.merchantImage).join(', ')} 파일만 올릴 수 있습니다.`
    );
  }
}

function failureMessage(error: unknown, fallback: string): string {
  const showable = error instanceof InputError || error instanceof BackendRequestError;
  return showable && error.message ? error.message : fallback;
}

// ── 상세 ──────────────────────────────────────────────────────────────────────

async function resolveThumbnailId(formData: FormData): Promise<string> {
  const file = readFiles(formData, 'thumbnailFile')[0] ?? null;
  if (!file) {
    return readString(formData, 'thumbnailFileId');
  }
  assertImage(file);
  return uploadAttachment(file, FILE_MODULE.merchantImage.id);
}

/** 저장된 이미지 한 장을 다시 파일로 — 묶음을 새 순서로 다시 올릴 때 쓴다. */
async function fetchSavedImage(fileId: string, fileSn: number): Promise<File> {
  const url = buildFileImageUrl(fileId, fileSn);
  if (!url) {
    throw new InputError(INVALID_REQUEST_MESSAGE);
  }
  const response = await fetch(url, { cache: 'no-store' });
  if (!response.ok) {
    throw new InputError('저장된 이미지를 다시 읽지 못했습니다. 잠시 후 다시 시도해 주세요.');
  }
  const blob = await response.blob();
  const extension = blob.type.split('/')[1] || 'jpg';
  return new File([blob], `image-${fileSn}.${extension}`, { type: blob.type });
}

/**
 * 추가 이미지 묶음. 백엔드가 한 `atchFileId` 아래 `fileSn` 1..n으로 채번하므로 순서·삭제·추가
 * 어느 것이든 바뀌면 묶음을 통째로 다시 올린다 — 저장된 장은 백엔드에서 다시 읽어 합친다.
 *
 * 폼: 칸마다 `imageSlot`(`saved:<fileSn>` | `new`)과 `imageFile`(같은 순서, 안 고른 칸은 빈 파일).
 */
async function resolveImageBundleId(formData: FormData): Promise<string> {
  const retainedId = readString(formData, 'imageFileId');
  const slots = formData
    .getAll('imageSlot')
    .map((entry) => (typeof entry === 'string' ? entry : ''));
  const files = readFiles(formData, 'imageFile');

  if (slots.length > MAX_EXTRA_IMAGES) {
    throw new InputError(IMAGES_TOO_MANY_MESSAGE);
  }

  const savedSns = slots
    .map((slot, index) =>
      files[index] === null && slot.startsWith('saved:') ? Number(slot.slice(6)) : null
    )
    .filter((sn): sn is number => sn !== null);
  const hasNewFile = files.some((file) => file !== null);
  const untouched =
    !hasNewFile &&
    savedSns.length === slots.length &&
    savedSns.every((sn, index) => sn === index + 1);

  if (slots.length === 0) {
    return '';
  }
  if (untouched) {
    return retainedId;
  }

  let newBytes = 0;
  const bundle: File[] = [];
  for (const [index, slot] of slots.entries()) {
    const picked = files[index];
    if (picked) {
      assertImage(picked);
      newBytes += picked.size;
      bundle.push(picked);
    } else if (slot.startsWith('saved:') && retainedId) {
      bundle.push(await fetchSavedImage(retainedId, Number(slot.slice(6))));
    }
  }
  if (newBytes > MAX_ATTACHMENT_BYTES) {
    throw new InputError(REQUEST_OVERSIZE_MESSAGE);
  }
  if (bundle.length === 0) {
    return '';
  }
  return uploadAttachments(bundle, FILE_MODULE.merchantImage.id);
}

function readDetailValues(
  formData: FormData,
  thumbnailFileId: string,
  imageFileId: string
): MerchantDetailValues {
  return {
    frcsNo: readString(formData, 'frcsNo'),
    categoryCode: readString(formData, 'categoryCode'),
    description: readString(formData, 'description'),
    openTime: readString(formData, 'openTime'),
    closeTime: readString(formData, 'closeTime'),
    closedDays: readString(formData, 'closedDays'),
    isVisible: readString(formData, 'isVisible') !== 'N',
    isVoucherUsable: readString(formData, 'isVoucherUsable') !== 'N',
    sortOrder: readOptionalInteger(formData, 'sortOrder'),
    thumbnailFileId,
    imageFileId,
  };
}

export async function saveMerchantDetailAction(
  _prevState: MerchantDetailFormState,
  formData: FormData
): Promise<MerchantDetailFormState> {
  await verifySession();

  const frcsNo = readString(formData, 'frcsNo');
  const isEdit = readString(formData, 'mode') === 'edit';
  if (!frcsNo) {
    return { status: 'error', errors: { frcsNo: '업체를 선택해 주세요.' } };
  }

  let failed: MerchantDetailFormState | null = null;
  try {
    const [thumbnailFileId, imageFileId, categories] = await Promise.all([
      resolveThumbnailId(formData),
      resolveImageBundleId(formData),
      fetchCommonCodes(CODE_GROUP.merchantType),
    ]);

    const validation = validateMerchantDetail(
      readDetailValues(formData, thumbnailFileId, imageFileId),
      categories
    );
    if (!validation.ok) {
      return { status: 'error', errors: validation.errors };
    }

    if (isEdit) {
      await updateMerchantDetail(validation.values);
    } else {
      await createMerchantDetail(validation.values);
    }
  } catch (error) {
    unstable_rethrow(error);
    console.error('[merchant-details] 저장 실패', error);
    failed = { status: 'error', message: failureMessage(error, SAVE_FAILED_MESSAGE) };
  }
  if (failed) {
    return failed;
  }

  revalidatePath(MERCHANT_DETAILS_PATH);
  revalidatePath(merchantDetailPath(frcsNo));
  // 등록·수정이 한 화면이라 여기서 옮기지 않는다 — 화면이 결과를 보고 머물거나 주소만 바꾼다.
  return { status: 'success', frcsNo, created: !isEdit };
}

export async function deleteMerchantDetailAction(
  frcsNo: string
): Promise<MerchantDetailActionResult> {
  await verifySession();
  if (!frcsNo) {
    return { status: 'error', message: INVALID_REQUEST_MESSAGE };
  }

  try {
    await deleteMerchantDetail(frcsNo);
  } catch (error) {
    unstable_rethrow(error);
    console.error('[merchant-details] 삭제 실패', error);
    return { status: 'error', message: failureMessage(error, DELETE_FAILED_MESSAGE) };
  }

  revalidatePath(MERCHANT_DETAILS_PATH);
  return { status: 'success' };
}

export async function deleteMerchantDetailsAction(
  frcsNos: string[]
): Promise<MerchantDetailActionResult> {
  await verifySession();
  const targets = frcsNos.filter((value) => typeof value === 'string' && value !== '');
  if (targets.length === 0) {
    return { status: 'error', message: INVALID_REQUEST_MESSAGE };
  }

  try {
    for (const frcsNo of targets) {
      await deleteMerchantDetail(frcsNo);
    }
  } catch (error) {
    unstable_rethrow(error);
    console.error('[merchant-details] 일괄 삭제 실패', error);
    return { status: 'error', message: failureMessage(error, DELETE_FAILED_MESSAGE) };
  }

  revalidatePath(MERCHANT_DETAILS_PATH);
  return { status: 'success' };
}

async function toggleDetailVisibility(frcsNo: string, isVisible: boolean): Promise<void> {
  const detail = await fetchMerchantDetail(frcsNo);
  // 대표이미지는 필수라 상세가 있으면 반드시 온다 — 없으면 백엔드가 상세를 돌려주지 않은 것이다.
  if (!detail || !detail.hasDetail || !detail.thumbnailFileId || !detail.categoryCode) {
    throw new InputError(DETAIL_NOT_LOADED_MESSAGE);
  }
  await updateMerchantDetail({
    frcsNo,
    categoryCode: detail.categoryCode,
    description: detail.description ?? '',
    openTime: detail.openTime ?? '',
    closeTime: detail.closeTime ?? '',
    closedDays: detail.closedDays ?? '',
    isVisible,
    isVoucherUsable: true,
    sortOrder: detail.sortOrder,
    thumbnailFileId: detail.thumbnailFileId,
    imageFileId: detail.imageFileId ?? '',
  });
}

export async function updateMerchantDetailVisibilityAction(
  frcsNo: string,
  isVisible: boolean
): Promise<MerchantDetailActionResult> {
  await verifySession();
  if (!frcsNo) {
    return { status: 'error', message: INVALID_REQUEST_MESSAGE };
  }

  try {
    await toggleDetailVisibility(frcsNo, isVisible);
  } catch (error) {
    unstable_rethrow(error);
    console.error('[merchant-details] 노출 변경 실패', error);
    return { status: 'error', message: failureMessage(error, SAVE_FAILED_MESSAGE) };
  }

  revalidatePath(MERCHANT_DETAILS_PATH);
  revalidatePath(merchantDetailPath(frcsNo));
  return { status: 'success' };
}

export async function deactivateMerchantDetailsAction(
  frcsNos: string[]
): Promise<MerchantDetailActionResult> {
  await verifySession();
  const targets = frcsNos.filter((value) => typeof value === 'string' && value !== '');
  if (targets.length === 0) {
    return { status: 'error', message: INVALID_REQUEST_MESSAGE };
  }

  try {
    for (const frcsNo of targets) {
      await toggleDetailVisibility(frcsNo, false);
    }
  } catch (error) {
    unstable_rethrow(error);
    console.error('[merchant-details] 일괄 비활성 실패', error);
    return { status: 'error', message: failureMessage(error, SAVE_FAILED_MESSAGE) };
  }

  revalidatePath(MERCHANT_DETAILS_PATH);
  return { status: 'success' };
}

// ── 메뉴 ──────────────────────────────────────────────────────────────────────

function readBadges(formData: FormData): MerchantProductBadge[] {
  return formData
    .getAll('badges')
    .map((entry) => (typeof entry === 'string' ? entry : ''))
    .filter((value): value is MerchantProductBadge =>
      MERCHANT_PRODUCT_BADGE_OPTIONS.some((option) => option.value === value)
    );
}

async function resolveProductImageId(formData: FormData): Promise<string> {
  const file = readFiles(formData, 'imageFile')[0] ?? null;
  if (!file) {
    return readString(formData, 'imageFileId');
  }
  assertImage(file);
  return uploadAttachment(file, FILE_MODULE.merchantImage.id);
}

export async function saveMerchantProductAction(
  _prevState: MerchantProductFormState,
  formData: FormData
): Promise<MerchantProductFormState> {
  await verifySession();

  const frcsNo = readString(formData, 'frcsNo');
  const productSn = readString(formData, 'productSn');
  if (!frcsNo) {
    return { status: 'error', message: INVALID_REQUEST_MESSAGE };
  }

  let failed: MerchantProductFormState | null = null;
  try {
    const imageFileId = await resolveProductImageId(formData);
    const validation = validateMerchantProduct({
      frcsNo,
      name: readString(formData, 'name'),
      price: readOptionalInteger(formData, 'price'),
      description: readString(formData, 'description'),
      badges: readBadges(formData),
      isVisible: readString(formData, 'isVisible') !== 'N',
      sortOrder: readOptionalInteger(formData, 'sortOrder'),
      imageFileId,
    });
    if (!validation.ok) {
      return { status: 'error', errors: validation.errors };
    }

    if (productSn) {
      await updateMerchantProduct(productSn, validation.values);
    } else {
      await createMerchantProduct(validation.values);
    }
  } catch (error) {
    unstable_rethrow(error);
    console.error('[merchant-products] 저장 실패', error);
    failed = { status: 'error', message: failureMessage(error, SAVE_FAILED_MESSAGE) };
  }
  if (failed) {
    return failed;
  }

  revalidatePath(merchantDetailPath(frcsNo));
  redirect(merchantDetailPath(frcsNo));
}

export async function deleteMerchantProductAction(
  frcsNo: string,
  productSn: string
): Promise<MerchantDetailActionResult> {
  await verifySession();
  if (!frcsNo || !productSn) {
    return { status: 'error', message: INVALID_REQUEST_MESSAGE };
  }

  try {
    await deleteMerchantProduct(productSn);
  } catch (error) {
    unstable_rethrow(error);
    console.error('[merchant-products] 삭제 실패', error);
    return { status: 'error', message: failureMessage(error, DELETE_FAILED_MESSAGE) };
  }

  revalidatePath(merchantDetailPath(frcsNo));
  return { status: 'success' };
}

export async function updateMerchantProductVisibilityAction(
  frcsNo: string,
  productSn: string,
  isVisible: boolean
): Promise<MerchantDetailActionResult> {
  await verifySession();
  if (!frcsNo || !productSn) {
    return { status: 'error', message: INVALID_REQUEST_MESSAGE };
  }

  try {
    const product = await fetchMerchantProduct(productSn);
    if (!product) {
      return { status: 'error', message: INVALID_REQUEST_MESSAGE };
    }
    await updateMerchantProduct(productSn, {
      frcsNo: product.frcsNo,
      name: product.name,
      price: product.price,
      description: product.description ?? '',
      badges: product.badges,
      isVisible,
      sortOrder: product.sortOrder,
      imageFileId: product.imageFileId ?? '',
    });
  } catch (error) {
    unstable_rethrow(error);
    console.error('[merchant-products] 노출 변경 실패', error);
    return { status: 'error', message: failureMessage(error, SAVE_FAILED_MESSAGE) };
  }

  revalidatePath(merchantDetailPath(frcsNo));
  return { status: 'success' };
}
