import 'server-only';
import { cache } from 'react';
import { getSessionAccessToken } from '@/lib/auth/dal';
import { BackendRequestError, backendFetch } from '@/lib/http/backend-fetch';
import type { BannedWord } from '@/lib/domain/banned-word';
import type { BannedWordQuery } from '@/lib/domain/banned-word-query';
import type { ExcelUploadResult } from '@/lib/domain/banned-word-form';

/**
 * 금칙어 Repository — `TB_SYS_BEN_WORD`.
 *
 * ```
 * GET    /api/v1/mngr/benWord/pagination      searchCondition=1 + searchKeyword로 부분검색
 * POST   /api/v1/mngr/benWord                 어노테이션 없음 → form. 중복이면 실패 응답
 * DELETE /api/v1/mngr/benWord/{benWordSn}     soft delete(DEL_YN)
 * POST   /api/v1/mngr/benWord/excel           multipart, 파트 이름 `file`
 * ```
 *
 * ⚠️ 백엔드 결함(보고함, 미수정)
 * 1. 목록 정렬이 기획과 반대다. `ROW_NUMBER() OVER (ORDER BY frst_reg_dt DESC)`로 매긴 rnum을
 *    다시 `ORDER BY RNUM DESC`로 뒤집어 **오래된 것이 먼저** 온다. 기획은 최신순이다. 그래서
 *    페이징을 백엔드에 맡기지 않고 한 번에 받아 여기서 정렬·절단한다(rnum 1 = 최신).
 * 2. `DELETE`의 인자에 `@PathVariable`이 없다. 경로만 보내면 benWordSn이 null이라 아무것도
 *    지워지지 않는다. 경로와 쿼리에 같은 값을 함께 실어 보낸다 — 어노테이션이 붙어도 그대로
 *    동작한다.
 */

const BEN_WORD_PATH = '/api/v1/mngr/benWord';

// TODO(백엔드): 정렬이 고쳐지면 서버 페이징으로 되돌린다. 그전까지는 이 상한을 넘는 금칙어가
// 생기면 목록이 조용히 잘린다.
const FETCH_LIMIT = 1000;

function isRecord(value: unknown): value is Record<string, unknown> {
  return value !== null && typeof value === 'object';
}

function readString(source: Record<string, unknown>, key: string): string | null {
  const value = source[key];
  return typeof value === 'string' && value.length > 0 ? value : null;
}

function toBannedWord(raw: unknown): BannedWord[] {
  if (!isRecord(raw)) {
    return [];
  }

  const id = Number(raw.benWordSn);
  if (!Number.isInteger(id)) {
    return [];
  }

  return [
    {
      id,
      word: readString(raw, 'benWordNm') ?? '',
      createdAt: readString(raw, 'frstRegDtStr'),
    },
  ];
}

/** rnum 1이 최신이라 오름차순이 곧 최신순이다. */
function byNewestFirst(raw: unknown): number {
  return isRecord(raw) ? Number(raw.rnum ?? 0) : 0;
}

const fetchAllBannedWords = cache(
  async (keyword: string): Promise<BannedWord[]> => {
    const accessToken = await getSessionAccessToken();

    const result = await backendFetch<unknown>(`${BEN_WORD_PATH}/pagination`, {
      method: 'GET',
      query: {
        pageIndex: 1,
        recordCountPerPage: FETCH_LIMIT,
        ...(keyword ? { searchCondition: '1', searchKeyword: keyword } : {}),
      },
      accessToken: accessToken ?? undefined,
      cache: 'no-store',
    });

    if (!result.ok) {
      throw new BackendRequestError(result);
    }

    const data = result.data;
    if (!isRecord(data) || !Array.isArray(data.list)) {
      throw new Error('금칙어 목록 응답의 형식이 올바르지 않습니다.');
    }

    return [...data.list]
      .sort((a, b) => byNewestFirst(a) - byNewestFirst(b))
      .flatMap(toBannedWord);
  }
);

export async function fetchBannedWords(
  query: BannedWordQuery
): Promise<{ items: BannedWord[]; totalCount: number }> {
  const all = await fetchAllBannedWords(query.keyword);
  const start = (query.page - 1) * query.pageSize;

  return {
    items: all.slice(start, start + query.pageSize),
    totalCount: all.length,
  };
}

export type CreateBannedWordResult =
  | { status: 'success' }
  | { status: 'duplicate' };

export async function createBannedWord(
  word: string
): Promise<CreateBannedWordResult> {
  const accessToken = await getSessionAccessToken();

  const result = await backendFetch<unknown>(BEN_WORD_PATH, {
    method: 'POST',
    form: { benWordNm: word },
    accessToken: accessToken ?? undefined,
    cache: 'no-store',
    canHaveNullData: true,
    canHaveEmptyBody: true,
  });

  if (!result.ok) {
    // 중복은 정상 흐름이라 예외로 올리지 않는다 — 백엔드가 "이미 등록된 단어 입니다"로 답한다.
    if (result.message.includes('이미 등록된')) {
      return { status: 'duplicate' };
    }
    throw new BackendRequestError(result);
  }

  return { status: 'success' };
}

export async function deleteBannedWord(id: number): Promise<void> {
  const accessToken = await getSessionAccessToken();

  const result = await backendFetch<unknown>(`${BEN_WORD_PATH}/${id}`, {
    method: 'DELETE',
    query: { benWordSn: id },
    accessToken: accessToken ?? undefined,
    cache: 'no-store',
    canHaveNullData: true,
    canHaveEmptyBody: true,
  });

  if (!result.ok) {
    throw new BackendRequestError(result);
  }
}

export async function uploadBannedWordExcel(
  file: File
): Promise<ExcelUploadResult> {
  const accessToken = await getSessionAccessToken();

  const multipart = new FormData();
  multipart.set('file', file);

  const result = await backendFetch<unknown>(`${BEN_WORD_PATH}/excel`, {
    method: 'POST',
    multipart,
    accessToken: accessToken ?? undefined,
    cache: 'no-store',
    canHaveNullData: true,
  });

  const data = result.ok ? result.data : null;
  const errors =
    isRecord(data) && Array.isArray(data.errors)
      ? data.errors.filter((entry): entry is string => typeof entry === 'string')
      : [];

  if (!result.ok) {
    // 검증 실패는 fail(300) + data.errors로 온다. 통신 오류와 구분해 화면에 그대로 보여준다.
    if (errors.length > 0) {
      return { successCount: 0, errors };
    }
    throw new BackendRequestError(result);
  }

  return {
    successCount:
      isRecord(data) && Number.isInteger(Number(data.successCount))
        ? Number(data.successCount)
        : 0,
    errors,
  };
}
