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

export const ERROR_LOG_TABLE = 'error_logs';
export const ERROR_LOG_VIEW_TOKEN = 'usr_7f3a9c2d8e1b4a6f';

export const createErrorLogSchema = z.object({
  source: z.enum(['server', 'client', 'automation']).default('server'),
  sourceLabel: z.string().trim().max(80).optional().nullable(),
  errorType: z.string().trim().min(1).max(120),
  errorName: z.string().trim().max(255).optional().nullable(),
  errorMessage: z.string().trim().min(1).max(10000),
  detail: z.string().trim().max(50000).optional().nullable(),
  stackTrace: z.string().trim().max(50000).optional().nullable(),
  statusCode: z.number().int().min(100).max(599).optional().nullable(),
  requestMethod: z.string().trim().max(10).optional().nullable(),
  requestPath: z.string().trim().max(1000).optional().nullable(),
  relatedPlanId: z.number().int().positive().optional().nullable(),
  relatedWorkId: z.string().trim().max(120).optional().nullable(),
  context: z.record(z.string(), z.unknown()).optional().nullable(),
});

export type ErrorLogPayload = z.infer<typeof createErrorLogSchema>;

async function ensureErrorLogTable() {
  const hasTable = await db.schema.hasTable(ERROR_LOG_TABLE);

  if (!hasTable) {
    await db.schema.createTable(ERROR_LOG_TABLE, (table) => {
      table.increments('id').primary();
      table.string('source', 20).notNullable().defaultTo('server');
      table.string('source_label', 80).nullable();
      table.string('error_type', 120).notNullable();
      table.string('error_name', 255).nullable();
      table.text('error_message').notNullable();
      table.text('detail').nullable();
      table.text('stack_trace').nullable();
      table.integer('status_code').nullable();
      table.string('request_method', 10).nullable();
      table.string('request_path', 1000).nullable();
      table.integer('related_plan_id').nullable();
      table.string('related_work_id', 120).nullable();
      table.jsonb('context_json').nullable();
      table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
    });

    return;
  }

  const requiredColumns: Array<[string, (table: any) => void]> = [
    ['source', (table) => table.string('source', 20).notNullable().defaultTo('server')],
    ['source_label', (table) => table.string('source_label', 80).nullable()],
    ['error_type', (table) => table.string('error_type', 120).notNullable().defaultTo('unknown')],
    ['error_name', (table) => table.string('error_name', 255).nullable()],
    ['error_message', (table) => table.text('error_message').notNullable().defaultTo('')],
    ['detail', (table) => table.text('detail').nullable()],
    ['stack_trace', (table) => table.text('stack_trace').nullable()],
    ['status_code', (table) => table.integer('status_code').nullable()],
    ['request_method', (table) => table.string('request_method', 10).nullable()],
    ['request_path', (table) => table.string('request_path', 1000).nullable()],
    ['related_plan_id', (table) => table.integer('related_plan_id').nullable()],
    ['related_work_id', (table) => table.string('related_work_id', 120).nullable()],
    ['context_json', (table) => table.jsonb('context_json').nullable()],
    ['created_at', (table) => table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(db.fn.now())],
  ];

  for (const [columnName, createColumn] of requiredColumns) {
    const hasColumn = await db.schema.hasColumn(ERROR_LOG_TABLE, columnName);

    if (!hasColumn) {
      await db.schema.alterTable(ERROR_LOG_TABLE, (table) => {
        createColumn(table);
      });
    }
  }
}

function normalizePayload(payload: ErrorLogPayload) {
  const parsedPayload = createErrorLogSchema.parse(payload);
  const sourceLabel =
    parsedPayload.sourceLabel ??
    (parsedPayload.source === 'client'
      ? '프론트엔드'
      : parsedPayload.source === 'automation'
        ? 'Plan 자동화'
        : '워크서버 API');

  return {
    source: parsedPayload.source,
    source_label: sourceLabel,
    error_type: parsedPayload.errorType,
    error_name: parsedPayload.errorName ?? null,
    error_message: parsedPayload.errorMessage,
    detail: parsedPayload.detail ?? null,
    stack_trace: parsedPayload.stackTrace ?? null,
    status_code: parsedPayload.statusCode ?? null,
    request_method: parsedPayload.requestMethod ?? null,
    request_path: parsedPayload.requestPath ?? null,
    related_plan_id: parsedPayload.relatedPlanId ?? null,
    related_work_id: parsedPayload.relatedWorkId ?? null,
    context_json: parsedPayload.context ?? null,
  };
}

function mapErrorLogRow(row: Record<string, unknown>) {
  return {
    id: Number(row.id),
    source: String(row.source ?? 'server'),
    sourceLabel: row.source_label ? String(row.source_label) : null,
    errorType: String(row.error_type ?? ''),
    errorName: row.error_name ? String(row.error_name) : null,
    errorMessage: String(row.error_message ?? ''),
    detail: row.detail ? String(row.detail) : null,
    stackTrace: row.stack_trace ? String(row.stack_trace) : null,
    statusCode: typeof row.status_code === 'number' ? row.status_code : row.status_code ? Number(row.status_code) : null,
    requestMethod: row.request_method ? String(row.request_method) : null,
    requestPath: row.request_path ? String(row.request_path) : null,
    relatedPlanId:
      typeof row.related_plan_id === 'number'
        ? row.related_plan_id
        : row.related_plan_id
          ? Number(row.related_plan_id)
          : null,
    relatedWorkId: row.related_work_id ? String(row.related_work_id) : null,
    context: row.context_json && typeof row.context_json === 'object' ? (row.context_json as Record<string, unknown>) : null,
    createdAt: row.created_at,
  };
}

export async function setupErrorLogTable() {
  await ensureErrorLogTable();

  return {
    ok: true,
    table: ERROR_LOG_TABLE,
  };
}

export async function createErrorLog(payload: ErrorLogPayload) {
  await ensureErrorLogTable();

  const [row] = await db(ERROR_LOG_TABLE)
    .insert({
      ...normalizePayload(payload),
      created_at: db.fn.now(),
    })
    .returning('*');

  return mapErrorLogRow(row);
}

export async function listErrorLogs(limit = 50) {
  await ensureErrorLogTable();

  const safeLimit = Number.isFinite(limit) ? Math.min(Math.max(Math.trunc(limit), 1), 200) : 50;
  const rows = await db(ERROR_LOG_TABLE).select('*').orderBy('created_at', 'desc').limit(safeLimit);

  return rows.map((row) => mapErrorLogRow(row));
}

export function hasErrorLogViewAccessToken(token: string | string[] | undefined) {
  const normalizedToken = Array.isArray(token) ? token[0] : token;
  return String(normalizedToken ?? '').trim() === ERROR_LOG_VIEW_TOKEN;
}
