import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import { db } from '../db/client.js';

type PlayAppEnvironment = 'preview' | 'test' | 'prod';

type PlayAppSeedEntry = {
  id: string;
  name: string;
  accentClassName: string;
  statusLabel: string;
  isReady: boolean;
  iconName: string;
  usagePriority?: number;
  supportedEnvironments: PlayAppEnvironment[];
  searchKeywords?: string[];
  searchDescription?: string;
};

const PLAY_APP_TABLE = 'play_apps';
const DEFAULT_ENTRIES: PlayAppSeedEntry[] = [
  {
    id: 'baseball-ticket-bay',
    name: '야구-티켓베이',
    accentClassName: 'apps-library__card--baseball-ticket-bay',
    statusLabel: '알림',
    isReady: true,
    iconName: 'BellOutlined',
    usagePriority: 100,
    supportedEnvironments: ['preview', 'test'],
    searchKeywords: ['야구', '티켓베이', 'ticketbay', '야구 티켓', '웹푸시', '알림'],
    searchDescription: '팀, 구역, 통로, 가격 조건으로 야구 티켓 알림 조건을 저장하고 테스트 푸시를 보냅니다.',
  },
  {
    id: 'e-reader',
    name: 'E-Reader',
    accentClassName: 'apps-library__card--reader',
    statusLabel: '읽기',
    isReady: true,
    iconName: 'BookOutlined',
    usagePriority: 80,
    supportedEnvironments: ['preview', 'test'],
    searchKeywords: ['e-reader', 'reader', 'ebook', 'article', 'web article', '기사', '전자책', '리더'],
    searchDescription: 'Apps 보관함에서 인터넷 기사와 웹 콘텐츠를 전자책처럼 넘겨 읽습니다.',
  },
  {
    id: 'photoprism',
    name: 'PhotoPrism',
    accentClassName: 'apps-library__card--photoprism',
    statusLabel: '연결',
    isReady: true,
    iconName: 'FileImageOutlined',
    usagePriority: 70,
    supportedEnvironments: ['preview', 'test'],
    searchKeywords: ['photoprism', 'photo', 'album', 'gallery', '사진 앨범'],
    searchDescription: 'Apps 보관함에서 PhotoPrism 앨범과 사진을 단독 앱 형태로 엽니다.',
  },
  {
    id: 'photo-puzzle',
    name: '사진 퍼즐',
    accentClassName: 'apps-library__card--puzzle',
    statusLabel: '실행',
    isReady: true,
    iconName: 'PictureOutlined',
    usagePriority: 60,
    supportedEnvironments: ['preview', 'test'],
    searchKeywords: ['photo puzzle', '퍼즐', '사진', '슬라이드 퍼즐'],
    searchDescription: 'Apps 보관함에서 사진 퍼즐 게임을 바로 실행합니다.',
  },
  {
    id: 'the-quest',
    name: 'The Quest',
    accentClassName: 'apps-library__card--the-quest',
    statusLabel: '신규',
    isReady: true,
    iconName: 'ThunderboltOutlined',
    usagePriority: 50,
    supportedEnvironments: ['preview', 'test'],
    searchKeywords: ['the quest', 'rpg', 'mobile rpg', 'phaser', 'quest', 'rpg game', '모바일 rpg'],
    searchDescription: 'Apps 보관함에서 한국형 모바일 RPG 데모 The Quest를 실행합니다.',
  },
  {
    id: 'template1',
    name: 'Template1',
    accentClassName: 'apps-library__card--template1',
    statusLabel: '템플릿',
    isReady: true,
    iconName: 'AppstoreAddOutlined',
    usagePriority: 45,
    supportedEnvironments: ['preview', 'test'],
    searchKeywords: ['template1', 'template', '앱 템플릿', '레이아웃', '기본 UI', 'layout'],
    searchDescription: '다른 앱 개발 시 공통 레이아웃을 빠르게 적용하기 위한 템플릿 화면입니다.',
  },
  {
    id: 'tetris',
    name: 'Tetris',
    accentClassName: 'apps-library__card--tetris',
    statusLabel: '실행',
    isReady: true,
    iconName: 'FundProjectionScreenOutlined',
    usagePriority: 40,
    supportedEnvironments: ['preview', 'test'],
    searchKeywords: ['테트리스', 'block', 'arcade', '블록 게임'],
    searchDescription: 'Apps 보관함에서 테트리스 게임을 바로 실행합니다.',
  },
  {
    id: 'beat-lab',
    name: 'Beat Lab',
    accentClassName: 'apps-library__card--beat',
    statusLabel: '준비',
    isReady: false,
    iconName: 'SoundOutlined',
    usagePriority: 35,
    supportedEnvironments: ['preview'],
    searchDescription: '향후 추가 예정인 Beat Lab 앱',
  },
  {
    id: 'sticker-booth',
    name: 'Sticker Booth',
    accentClassName: 'apps-library__card--sticker',
    statusLabel: '준비',
    isReady: false,
    iconName: 'StarOutlined',
    usagePriority: 30,
    supportedEnvironments: ['preview'],
    searchDescription: '향후 추가 예정인 Sticker Booth 앱',
  },
  {
    id: 'launch-note',
    name: 'Launch Note',
    accentClassName: 'apps-library__card--launch',
    statusLabel: '예정',
    isReady: false,
    iconName: 'RocketOutlined',
    usagePriority: 20,
    supportedEnvironments: ['preview'],
    searchDescription: '향후 추가 예정인 Launch Note 앱',
  },
  {
    id: 'arcade-pack',
    name: 'Arcade Pack',
    accentClassName: 'apps-library__card--arcade',
    statusLabel: '예정',
    isReady: false,
    iconName: 'FireOutlined',
    usagePriority: 10,
    supportedEnvironments: ['preview'],
    searchDescription: '향후 추가 예정인 Arcade Pack 앱',
  },
  {
    id: 'app-vault',
    name: 'App Vault',
    accentClassName: 'apps-library__card--vault',
    statusLabel: '테마',
    isReady: false,
    iconName: 'AppstoreOutlined',
    usagePriority: 0,
    supportedEnvironments: ['preview'],
    searchDescription: '향후 추가 예정인 App Vault 앱',
  },
] ;

const listQuerySchema = z.object({
  environment: z.enum(['preview', 'test', 'prod']).optional(),
});

const playAppIdSchema = z.string().trim().min(1).max(100);

const supportedEnvironmentSchema = z.enum(['preview', 'test', 'prod']);

const iconNameSchema = z.enum([
  'AppstoreOutlined',
  'AppstoreAddOutlined',
  'BellOutlined',
  'BookOutlined',
  'FireOutlined',
  'FundProjectionScreenOutlined',
  'FileImageOutlined',
  'PictureOutlined',
  'RocketOutlined',
  'SoundOutlined',
  'StarOutlined',
  'ThunderboltOutlined',
]);

const playAppCreatePayloadSchema = z.object({
  id: playAppIdSchema,
  name: z.string().trim().min(1).max(120),
  accentClassName: z.string().trim().min(1).max(80),
  statusLabel: z.string().trim().min(1).max(80),
  isReady: z.boolean().default(false),
  iconName: iconNameSchema,
  usagePriority: z.number().int().min(0).max(1_000_000).optional(),
  supportedEnvironments: z.array(supportedEnvironmentSchema).min(1).default(['preview']),
  searchKeywords: z
    .array(z.string().trim().min(1).max(80))
    .default([])
    .transform((keywords) => Array.from(new Set(keywords.map((keyword) => keyword.trim()).filter((keyword) => keyword.length > 0)))),
  searchDescription: z.string().trim().max(4000).default(''),
});

const playAppUpdatePayloadSchema = z
  .object({
    id: playAppIdSchema.optional(),
    name: z.string().trim().min(1).max(120).optional(),
    accentClassName: z.string().trim().min(1).max(80).optional(),
    statusLabel: z.string().trim().min(1).max(80).optional(),
    isReady: z.boolean().optional(),
    iconName: iconNameSchema.optional(),
    usagePriority: z.number().int().min(0).max(1_000_000).nullable().optional(),
    supportedEnvironments: z.array(supportedEnvironmentSchema).optional(),
    searchKeywords: z
      .array(z.string().trim().min(1).max(80))
      .transform((keywords) => Array.from(new Set(keywords.map((keyword) => keyword.trim()).filter((keyword) => keyword.length > 0))))
      .optional(),
    searchDescription: z.string().trim().max(4000).optional(),
  })
  .refine((payload) => Object.keys(payload).length > 0, {
    message: '수정할 항목이 없습니다.',
  });

const playAppIdParamsSchema = z.object({
  id: playAppIdSchema,
});

type SupportedEnvironment = Array<PlayAppEnvironment>;
type PlayAppRegistryRecord = {
  id: string;
  name: string;
  accent_class_name: string;
  status_label: string;
  is_ready: boolean;
  icon_name: string;
  usage_priority: number | null;
  supported_environments: string | null;
  search_keywords: string | null;
  search_description: string | null;
};

type PlayAppRow = {
  id: string;
  name: string;
  accentClassName: string;
  statusLabel: string;
  isReady: boolean;
  iconName: string;
  usagePriority?: number;
  supportedEnvironments: SupportedEnvironment;
  searchKeywords: string[];
  searchDescription: string;
};

type PlayAppCreateInput = z.infer<typeof playAppCreatePayloadSchema>;
type PlayAppUpdateInput = z.infer<typeof playAppUpdatePayloadSchema>;

function normalizeSupportedEnvironments(value: string | null): SupportedEnvironment {
  if (!value) {
    return ['preview'];
  }

  const trimmed = value.trim();
  if (!trimmed) {
    return ['preview'];
  }

  if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
    try {
      const parsed = JSON.parse(trimmed);
      if (Array.isArray(parsed)) {
        return parsed
          .map((item) => String(item).trim())
          .filter((item): item is PlayAppEnvironment => item === 'preview' || item === 'test' || item === 'prod');
      }
    } catch {
      // fall through to comma parser below.
    }
  }

  return value
    .trim()
    .split(',')
    .map((item) => item.trim().toLowerCase())
    .filter((item): item is PlayAppEnvironment => item === 'preview' || item === 'test' || item === 'prod');
}

function parseJsonArrayList(value: string | null): string[] {
  if (!value) {
    return [];
  }

  const trimmed = value.trim();
  if (!trimmed) {
    return [];
  }

  if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
    try {
      const parsed = JSON.parse(trimmed);
      if (Array.isArray(parsed)) {
        return parsed
          .map((item) => (typeof item === 'string' ? item.trim() : ''))
          .filter((item) => item.length > 0);
      }
    } catch {
      // fallthrough to legacy parser below.
    }
  }

  return trimmed
    .split(',')
    .map((item) => item.trim())
    .filter((item) => item.length > 0);
}

function toResponseItem(row: PlayAppRegistryRecord): PlayAppRow {
  return {
    id: row.id,
    name: row.name,
    accentClassName: row.accent_class_name,
    statusLabel: row.status_label,
    isReady: !!row.is_ready,
    iconName: row.icon_name,
    usagePriority: row.usage_priority ?? undefined,
    supportedEnvironments: normalizeSupportedEnvironments(row.supported_environments),
    searchKeywords: parseJsonArrayList(row.search_keywords),
    searchDescription: row.search_description ?? '',
  };
}

function encodeJsonList(values: readonly string[] | null | undefined) {
  if (!values || values.length === 0) {
    return '[]';
  }

  return JSON.stringify(Array.from(new Set(values.map((value) => value.trim()).filter(Boolean))));
}

function toDbRowPayload(input: PlayAppCreateInput | Omit<PlayAppUpdateInput, 'id'>) {
  const payload: Record<string, unknown> = {
    id: 'id' in input ? input.id?.trim() : undefined,
    name: input.name?.trim(),
    accent_class_name: input.accentClassName?.trim(),
    status_label: input.statusLabel?.trim(),
    is_ready: input.isReady,
    icon_name: input.iconName,
    usage_priority: input.usagePriority ?? null,
    supported_environments: input.supportedEnvironments ? encodeJsonList(input.supportedEnvironments) : undefined,
    search_keywords: input.searchKeywords ? encodeJsonList(input.searchKeywords) : undefined,
    search_description: input.searchDescription ? input.searchDescription.trim() : undefined,
  };

  Object.entries(payload).forEach(([key, value]) => {
    if (value === undefined) {
      delete payload[key];
    }
  });

  if ('is_ready' in payload && payload.is_ready === undefined) {
    payload.is_ready = false;
  }

  return payload;
}

function isDbUpdateResultEmpty(result: unknown) {
  if (Array.isArray(result)) {
    return result.length === 0;
  }

  return typeof result === 'number' ? result === 0 : false;
}

function parsePlayAppErrorWithCode(error: unknown, fallbackMessage: string) {
  if (error instanceof Error) {
    const code = (error as { code?: string }).code;
    if (code === 'ER_DUP_ENTRY' || code === '23505') {
      const duplicateError = error as Error & { statusCode?: number; details?: string };
      duplicateError.statusCode = 409;
      duplicateError.message = '이미 등록된 앱 ID입니다.';
      return duplicateError;
    }
  }

  if (error instanceof Error) {
    const duplicateError = error as Error & { statusCode?: number; details?: string };
    duplicateError.message = fallbackMessage;
    return duplicateError;
  }

  const next = new Error(fallbackMessage) as Error & { statusCode?: number };
  next.statusCode = 500;
  return next;
}

async function ensurePlayAppTable() {
  const exists = await db.schema.hasTable(PLAY_APP_TABLE);
  if (!exists) {
    await db.schema.createTable(PLAY_APP_TABLE, (table) => {
      table.string('id', 100).primary();
      table.string('name', 120).notNullable();
      table.string('accent_class_name', 80).notNullable();
      table.string('status_label', 80).notNullable();
      table.boolean('is_ready').notNullable().defaultTo(false);
      table.string('icon_name', 80).notNullable();
      table.integer('usage_priority').nullable();
      table.text('supported_environments').nullable();
      table.text('search_keywords').nullable();
      table.text('search_description').nullable();
      table.index('is_ready', 'play_apps_is_ready_idx');
    });
  }

  const existingRows = await db(PLAY_APP_TABLE).select('id');
  const existingIds = new Set(existingRows.map((row) => row.id));

  const rowsToInsert = DEFAULT_ENTRIES.filter((entry) => !existingIds.has(entry.id)).map((entry) => ({
    id: entry.id,
    name: entry.name,
    accent_class_name: entry.accentClassName,
    status_label: entry.statusLabel,
    is_ready: entry.isReady,
    icon_name: entry.iconName,
    usage_priority: entry.usagePriority,
    supported_environments: encodeJsonList(entry.supportedEnvironments),
    search_keywords: encodeJsonList(entry.searchKeywords ?? []),
    search_description: entry.searchDescription ?? '',
  }));

  if (rowsToInsert.length === 0) {
    return;
  }

  await db(PLAY_APP_TABLE).insert(rowsToInsert);
}

async function listPlayAppEntries(environment?: PlayAppEnvironment | null) {
  await ensurePlayAppTable();

  const rows = (await db(PLAY_APP_TABLE)
    .select<PlayAppRegistryRecord[]>('*')
    .orderBy('usage_priority', 'desc')
    .orderBy('id', 'asc')) as PlayAppRegistryRecord[];

  const normalizedRows = rows.map(toResponseItem);
  const filteredRows = environment ? normalizedRows.filter((row) => row.supportedEnvironments.includes(environment)) : normalizedRows;

  return filteredRows;
}

async function createPlayAppEntry(body: unknown) {
  await ensurePlayAppTable();

  const payload = playAppCreatePayloadSchema.parse(body);
  const dbPayload = toDbRowPayload(payload);

  try {
    await db(PLAY_APP_TABLE).insert(dbPayload);
  } catch (error) {
    throw parsePlayAppErrorWithCode(error, `앱 등록에 실패했습니다: ${payload.id}`);
  }

  const insertedRow = await db(PLAY_APP_TABLE)
    .where({ id: payload.id })
    .first<PlayAppRegistryRecord>();

  if (!insertedRow) {
    const notFoundError = new Error('등록된 앱을 조회하지 못했습니다.') as Error & { statusCode?: number };
    notFoundError.statusCode = 500;
    throw notFoundError;
  }

  return {
    ok: true,
    item: toResponseItem(insertedRow),
  };
}

async function updatePlayAppEntry(params: unknown, body: unknown) {
  await ensurePlayAppTable();

  const { id } = playAppIdParamsSchema.parse(params);
  const payload = playAppUpdatePayloadSchema.parse(body);

  if (payload.id && payload.id !== id) {
    const invalidError = new Error('요청 경로 ID와 본문 ID가 일치하지 않습니다.') as Error & { statusCode?: number };
    invalidError.statusCode = 409;
    throw invalidError;
  }

  const dbPayload = toDbRowPayload(payload);
  const updated = await db(PLAY_APP_TABLE).where({ id }).update(dbPayload as Record<string, unknown>);

  if (isDbUpdateResultEmpty(updated)) {
    const notFoundError = new Error(`수정할 앱을 찾을 수 없습니다: ${id}`) as Error & { statusCode?: number };
    notFoundError.statusCode = 404;
    throw notFoundError;
  }

  const updatedRow = await db(PLAY_APP_TABLE).where({ id }).first<PlayAppRegistryRecord>();
  if (!updatedRow) {
    const notFoundError = new Error(`수정한 앱을 조회할 수 없습니다: ${id}`) as Error & { statusCode?: number };
    notFoundError.statusCode = 404;
    throw notFoundError;
  }

  return {
    ok: true,
    item: toResponseItem(updatedRow),
  };
}

async function deletePlayAppEntry(params: unknown) {
  await ensurePlayAppTable();

  const { id } = playAppIdParamsSchema.parse(params);
  const deleted = await db(PLAY_APP_TABLE).where({ id }).delete('*');
  if (isDbUpdateResultEmpty(deleted)) {
    const notFoundError = new Error(`삭제할 앱을 찾을 수 없습니다: ${id}`) as Error & { statusCode?: number };
    notFoundError.statusCode = 404;
    throw notFoundError;
  }

  if (Array.isArray(deleted) && deleted[0]) {
    return {
      ok: true,
      deletedId: id,
      item: toResponseItem(deleted[0] as PlayAppRegistryRecord),
    };
  }

  return {
    ok: true,
    deletedId: id,
  };
}

export async function registerPlayAppRoutes(app: FastifyInstance) {
  app.get('/api/play-apps', async (request) => {
    const query = listQuerySchema.parse(request.query);

    const items = await listPlayAppEntries(query.environment);

    return {
      ok: true,
      items,
    };
  });

  app.post('/api/play-apps', async (request) => {
    return createPlayAppEntry(request.body);
  });

  app.put('/api/play-apps/:id', async (request) => {
    return updatePlayAppEntry(request.params, request.body);
  });

  app.delete('/api/play-apps/:id', async (request) => {
    return deletePlayAppEntry(request.params);
  });
}
