import 'server-only';
import { getSessionAccessToken } from '@/lib/auth/dal';
import { BackendRequestError, backendFetch } from '@/lib/http/backend-fetch';
import { buildFileImageUrl } from '@/lib/data/repositories/file-repository';
import type { ContentItem, ContentKeyword } from '@/lib/domain/content';
import type { ContentQuery } from '@/lib/domain/content-query';
import type {
  ComicFormValues,
  ShortsFormValues,
} from '@/lib/domain/content-form';

/**
 * 금융쇼츠·4컷금융만화 Repository — 두 화면은 같은 테이블(`TB_COM_CNTNTS`)을 쓰고 경로만
 * 갈린다. 금융OX퀴즈는 테이블도 API도 달라 `content-quiz-repository.ts`가 따로 맡는다.
 *
 * ```
 * GET /api/v1/mngr/cntntns/toon/pagination    4컷금융만화 목록
 * GET /api/v1/mngr/cntntns/video/pagination   금융쇼츠 목록
 * ```
 *
 * 이슈: 경로가 `cntnts`가 아니라 **`cntntns`**다(백엔드 오타). 고쳐지면 아래 상수만 바꾼다.
 * 이슈: `video/pagination` 컨트롤러가 `return null;`이라 금융쇼츠 목록은 늘 비어 온다.
 *   서비스가 주입돼 있지 않아 프론트에서 우회할 방법이 없다.
 * 이슈: 등록·수정·삭제·단건조회 API가 없다. 아래 쓰기 함수는 다른 관리 화면과 같은 규약
 *   (`POST {base}` / `PUT {base}/{sn}` / `DELETE {base}/{sn}`)으로 미리 맞춰 둔 것이라
 *   백엔드가 생기기 전에는 실패한다.
 * 이슈: 학교급·학년은 컬럼도 SQL도 없다(검색 VO에 파라미터만 있고 매퍼의 `<if>`가 비어 있다).
 *   주제·전달메시지도 컬럼이 없어 목록·상세에서 늘 빈 값이다.
 * 이슈: toon 매퍼의 검색 조건이 금칙어 테이블 컬럼(`BEN_WORD_NM`)을 보고 있어 검색어가 걸리면
 *   SQL이 깨진다. 그래서 검색어는 보내되 백엔드 수정 전까지는 결과를 신뢰할 수 없다.
 * 이슈: 목록 SQL이 키워드를 LEFT JOIN해 행이 키워드 수만큼 늘어나는데 count는 조인 없이 센다.
 *   한 페이지에 실제로 오는 콘텐츠 수가 `recordCountPerPage`보다 적을 수 있다.
 */

const CONTENT_BASE_PATH = '/api/v1/mngr/cntntns';

const SEGMENT: Record<'shorts' | 'comic', string> = {
  shorts: 'video',
  comic: 'toon',
};

type ContentApiKind = keyof typeof SEGMENT;

/** 단건 조회 API가 없어 목록에서 찾는다. 그때 한 번에 받아올 최대 행 수. */
const DETAIL_SCAN_LIMIT = 500;

function basePath(kind: ContentApiKind): string {
  return `${CONTENT_BASE_PATH}/${SEGMENT[kind]}`;
}

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 toKeywords(raw: unknown): ContentKeyword[] {
  if (!Array.isArray(raw)) {
    return [];
  }
  return raw.flatMap((entry) => {
    if (!isRecord(entry)) {
      return [];
    }
    const keyword = readString(entry, 'cntntsKeyword');
    if (!keyword) {
      return [];
    }
    return [{ keyword, description: readString(entry, 'cntntsExpln') ?? '' }];
  });
}

function toContentItem(raw: unknown): ContentItem {
  if (!isRecord(raw)) {
    throw new Error('콘텐츠 응답 항목의 형식이 올바르지 않습니다.');
  }

  const cntntsSn = readString(raw, 'cntntsSn');
  if (!cntntsSn) {
    throw new Error('콘텐츠 응답에 cntntsSn이 없습니다.');
  }

  return {
    cntntsSn,
    name: readString(raw, 'cntntsNm') ?? '',
    title: readString(raw, 'cntntsTitle') ?? '',
    subject: readString(raw, 'cntntsSbjct') ?? '',
    message: readString(raw, 'cntntsMssage') ?? '',
    schoolLevel: readString(raw, 'schulGradeCd') ?? '',
    grades: (readString(raw, 'gradeCd') ?? '')
      .split(',')
      .map((grade) => grade.trim())
      .filter(Boolean),
    keywords: toKeywords(raw.keywordList),
    attachmentId: readString(raw, 'atchFileId'),
    thumbnailFileId: readString(raw, 'thumbAtchFileId'),
    isVisible: readString(raw, 'useYn') !== 'N',
    modifiedAt:
      readString(raw, 'lastMdfcnDtStr') ?? readString(raw, 'frstRegDtStr'),
  };
}

function buildListParams(
  query: ContentQuery,
  pageIndex: number,
  recordCountPerPage: number
): Record<string, string | number> {
  const keyword = query.keyword.trim();

  return {
    searchSchulGradeCd: query.schoolLevel,
    searchGradeCd: query.grade,
    searchCondition: keyword ? '1' : '',
    searchKeyword: keyword,
    // 이슈: 백엔드가 정렬 파라미터를 읽지 않는다(등록일 내림차순 고정). 화면 선택지는 기획대로
    // 유지하고 값만 미리 실어 보낸다.
    searchSortOrder: query.sort,
    pageIndex,
    recordCountPerPage,
  };
}

async function requestList(
  kind: ContentApiKind,
  query: ContentQuery,
  pageIndex: number,
  recordCountPerPage: number
): Promise<{ items: ContentItem[]; reportedTotalCount: number }> {
  const accessToken = await getSessionAccessToken();

  const result = await backendFetch<unknown>(`${basePath(kind)}/pagination`, {
    method: 'GET',
    query: buildListParams(query, pageIndex, recordCountPerPage),
    accessToken: accessToken ?? undefined,
    cache: 'no-store',
    canHaveNullData: true,
  });

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

  const data = result.data;
  // 금융쇼츠는 컨트롤러가 null을 돌려준다(파일 상단 이슈) — 빈 목록으로 다룬다.
  if (data === null || data === undefined) {
    return { items: [], reportedTotalCount: 0 };
  }
  if (!isRecord(data) || !Array.isArray(data.list)) {
    throw new Error('콘텐츠 목록 응답의 형식이 올바르지 않습니다.');
  }

  return {
    items: data.list.map(toContentItem),
    reportedTotalCount:
      typeof data.totalCount === 'number' ? data.totalCount : data.list.length,
  };
}

export type ContentPage = {
  items: ContentItem[];
  totalCount: number;
  isTotalCountExact: boolean;
};

export async function fetchContents(
  kind: ContentApiKind,
  query: ContentQuery
): Promise<ContentPage> {
  const { items, reportedTotalCount } = await requestList(
    kind,
    query,
    query.page,
    query.pageSize
  );

  const confirmedCount = (query.page - 1) * query.pageSize + items.length;

  return {
    items,
    // 백엔드 `PaginationUtil`이 count 없이 페이지 행 수를 총건수로 내려주는 결함이 있어
    // 하한값으로 보정한다(다른 목록 화면과 같은 처리).
    totalCount: Math.max(reportedTotalCount, confirmedCount),
    isTotalCountExact:
      items.length < query.pageSize || reportedTotalCount > confirmedCount,
  };
}

/**
 * 단건 조회.
 *
 * TODO: `GET {base}/{cntntsSn}`이 생기면 이 스캔을 그 호출로 교체한다. 지금은 단건 API가 없어
 * 목록에서 찾는다 — 목록이 돌려주는 필드만 채워지고 나머지는 빈 값이다.
 */
export async function fetchContentDetail(
  kind: ContentApiKind,
  cntntsSn: string
): Promise<ContentItem | null> {
  const { items } = await requestList(
    kind,
    {
      schoolLevel: '',
      grade: '',
      level: '',
      searchField: '',
      keyword: '',
      sort: 'created',
      page: 1,
      pageSize: 10,
    },
    1,
    DETAIL_SCAN_LIMIT
  );

  return items.find((item) => item.cntntsSn === cntntsSn) ?? null;
}

type ContentWritePayload = {
  cntntsType?: string;
  cntntsNm: string;
  cntntsTitle: string;
  cntntsSbjct: string;
  cntntsMssage: string;
  schulGradeCd: string;
  gradeCd: string;
  atchFileId: string;
  thumbAtchFileId: string;
  useYn: string;
  keywordList: Array<{ cntntsKeyword: string; cntntsExpln: string }>;
};

function toKeywordPayload(keywords: readonly ContentKeyword[]) {
  return keywords.map((item) => ({
    cntntsKeyword: item.keyword,
    cntntsExpln: item.description,
  }));
}

export function buildShortsPayload(
  values: ShortsFormValues
): ContentWritePayload {
  return {
    cntntsNm: values.title,
    cntntsTitle: values.title,
    cntntsSbjct: values.subject,
    cntntsMssage: values.message,
    schulGradeCd: values.schoolLevel,
    gradeCd: values.grades.join(','),
    atchFileId: values.videoFileId,
    thumbAtchFileId: values.thumbnailFileId,
    useYn: values.isVisible ? 'Y' : 'N',
    keywordList: toKeywordPayload(values.keywords),
  };
}

export function buildComicPayload(values: ComicFormValues): ContentWritePayload {
  return {
    cntntsNm: values.name,
    cntntsTitle: values.name,
    cntntsSbjct: values.subject,
    cntntsMssage: values.message,
    schulGradeCd: values.schoolLevel,
    gradeCd: values.grades.join(','),
    // 이슈: 기획은 "각 컷을 개별 이미지로 업로드"인데 백엔드 컬럼은 `atchFileId` 한 칸뿐이라
    // 컷 묶음을 여러 개 연결할 자리가 없다. 지금은 첫 묶음만 저장된다.
    atchFileId: values.cutFileIds[0] ?? '',
    thumbAtchFileId: values.thumbnailFileId,
    useYn: values.isVisible ? 'Y' : 'N',
    keywordList: toKeywordPayload(values.keywords),
  };
}

async function sendWrite(
  path: string,
  method: 'POST' | 'PUT' | 'DELETE',
  body?: unknown
): Promise<string | null> {
  const accessToken = await getSessionAccessToken();

  const result = await backendFetch<unknown>(path, {
    method,
    ...(body === undefined ? {} : { body }),
    accessToken: accessToken ?? undefined,
    cache: 'no-store',
    canHaveNullData: true,
    canHaveEmptyBody: true,
  });

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

  return typeof result.data === 'string' ? result.data : null;
}

export async function createContent(
  kind: ContentApiKind,
  payload: ContentWritePayload,
  cntntsType: string
): Promise<string | null> {
  return sendWrite(basePath(kind), 'POST', { ...payload, cntntsType });
}

export async function updateContent(
  kind: ContentApiKind,
  cntntsSn: string,
  payload: ContentWritePayload
): Promise<void> {
  await sendWrite(
    `${basePath(kind)}/${encodeURIComponent(cntntsSn)}`,
    'PUT',
    payload
  );
}

export async function deleteContent(
  kind: ContentApiKind,
  cntntsSn: string
): Promise<void> {
  await sendWrite(`${basePath(kind)}/${encodeURIComponent(cntntsSn)}`, 'DELETE');
}

export function contentThumbnailUrl(item: ContentItem): string | null {
  return buildFileImageUrl(item.thumbnailFileId);
}

export function contentCutUrls(item: ContentItem, count: number): string[] {
  return Array.from({ length: count }, (_, index) =>
    buildFileImageUrl(item.attachmentId, index + 1)
  ).filter((url): url is string => url !== null);
}
