import { db } from '../db/client.js';
import { getAppConfig } from './app-config-service.js';

const AUTOMATION_TYPES_TABLE = 'automation_types';
const AUTOMATION_TYPES_CONFIG_KEY = 'automationTypes';

export const AUTOMATION_BEHAVIOR_TYPES = [
  'none',
  'plan',
  'command_execution',
  'non_source_work',
  'auto_worker',
] as const;

export type AutomationBehaviorType = (typeof AUTOMATION_BEHAVIOR_TYPES)[number];

export type AutomationTypeContextRecord = {
  id: string;
  title: string;
  content: string;
  enabled: boolean;
  defaultSelected: boolean;
  updatedAt: string;
};

export type AutomationTypeRecord = {
  id: string;
  name: string;
  description: string;
  contexts: AutomationTypeContextRecord[];
  behaviorType: AutomationBehaviorType;
  enabled: boolean;
  updatedAt: string;
};

export const DEFAULT_AUTOMATION_TYPES: AutomationTypeRecord[] = [
  {
    id: 'general-inquiry',
    name: '일반 문의',
    description: '일반 문의/확인 요청으로 처리합니다.',
    contexts: [
      {
        id: 'general-inquiry-default',
        title: '기본 확인',
        content: '소스 수정 없이 조회, 상태 확인, 응답 정리를 우선합니다.',
        enabled: true,
        defaultSelected: true,
        updatedAt: '2026-04-29T00:00:00.000Z',
      },
    ],
    behaviorType: 'command_execution',
    enabled: true,
    updatedAt: '2026-04-29T00:00:00.000Z',
  },
  {
    id: 'none',
    name: '기본유형',
    description: '기본 자동화 처리용 유형입니다.',
    contexts: [
      {
        id: 'none-default',
        title: '기본 처리',
        content:
          '## 기본 처리\n- 현재 저장소 규칙과 요청 본문을 우선 따릅니다.\n- 소스 수정이 필요하면 관련 파일을 수정하고, 필요 없으면 확인 결과만 정리합니다.\n- 작업 결과에는 변경 파일, diff, preview 같은 증적을 남길 수 있습니다.',
        enabled: true,
        defaultSelected: true,
        updatedAt: '2026-04-29T00:00:00.000Z',
      },
    ],
    behaviorType: 'none',
    enabled: true,
    updatedAt: '2026-04-23T00:00:00.000Z',
  },
  {
    id: 'plan',
    name: '작업 요청 등록',
    description: 'Plan 등록/문서형 자동화 요청으로 처리합니다.',
    contexts: [
      {
        id: 'plan-default',
        title: '문서형 처리',
        content: 'Plan 등록/문서형 요청으로 처리하고, 구현보다 등록/정리 결과를 우선 남깁니다.',
        enabled: true,
        defaultSelected: true,
        updatedAt: '2026-04-29T00:00:00.000Z',
      },
    ],
    behaviorType: 'plan',
    enabled: true,
    updatedAt: '2026-04-23T00:00:00.000Z',
  },
  {
    id: 'command_execution',
    name: 'Command 실행',
    description: '저장소 수정 없이 명령 실행/조회 중심으로 처리합니다.',
    contexts: [
      {
        id: 'command-execution-default',
        title: '명령 실행',
        content: '저장소 수정 없이 명령 실행, API 확인, 상태 조회 중심으로 처리합니다.',
        enabled: true,
        defaultSelected: true,
        updatedAt: '2026-04-29T00:00:00.000Z',
      },
    ],
    behaviorType: 'command_execution',
    enabled: true,
    updatedAt: '2026-04-23T00:00:00.000Z',
  },
  {
    id: 'non_source_work',
    name: '비 소스작업',
    description: '문서/운영/확인 등 비소스 작업으로 처리합니다.',
    contexts: [
      {
        id: 'non-source-work-default',
        title: '비소스 작업',
        content: '문서, 운영, 데이터 확인 등 비소스 작업으로 처리하고 코드 수정은 요청 시에만 제한적으로 수행합니다.',
        enabled: true,
        defaultSelected: true,
        updatedAt: '2026-04-29T00:00:00.000Z',
      },
    ],
    behaviorType: 'non_source_work',
    enabled: true,
    updatedAt: '2026-04-23T00:00:00.000Z',
  },
  {
    id: 'auto_worker',
    name: 'autoWorker',
    description: '자동화 작업메모로 처리합니다.',
    contexts: [
      {
        id: 'auto-worker-default',
        title: '자동화 기본 규칙',
        content:
          '## context 사용 규칙\n- 자동화 실행기는 선택된 자동화 유형의 context만 우선 참조합니다.\n- Codex Live 문맥이나 일반 채팅 문맥은 자동화 기본 context로 섞지 않습니다.\n\n## 처리 원칙\n- 세부 절차는 현재 운영 설정을 따릅니다.',
        enabled: true,
        defaultSelected: true,
        updatedAt: '2026-04-29T00:00:00.000Z',
      },
    ],
    behaviorType: 'auto_worker',
    enabled: true,
    updatedAt: '2026-04-23T00:00:00.000Z',
  },
];

function normalizeText(value: unknown) {
  return typeof value === 'string' ? value.trim() : '';
}

function normalizeEnabled(value: unknown) {
  if (typeof value === 'boolean') {
    return value;
  }

  if (typeof value === 'number') {
    return value !== 0;
  }

  if (typeof value === 'string') {
    const normalizedValue = value.trim().toLowerCase();

    if (['false', '0', 'n', 'no', 'off'].includes(normalizedValue)) {
      return false;
    }

    if (['true', '1', 'y', 'yes', 'on'].includes(normalizedValue)) {
      return true;
    }
  }

  return value !== false;
}

function normalizeLegacyAutomationTypeId(value: unknown) {
  const normalizedValue = normalizeLegacyAutomationBehaviorType(value);

  if (normalizedValue === 'stock-alert') {
    return 'general-inquiry';
  }

  return normalizedValue;
}

function normalizeBehaviorType(value: unknown): AutomationBehaviorType {
  const normalizedValue = normalizeLegacyAutomationBehaviorType(value);
  return AUTOMATION_BEHAVIOR_TYPES.includes(normalizedValue as AutomationBehaviorType)
    ? (normalizedValue as AutomationBehaviorType)
    : 'none';
}

export function normalizeLegacyAutomationBehaviorType(value: unknown): string {
  const normalizedValue = normalizeText(value);

  if (normalizedValue === 'plan_registration') {
    return 'plan';
  }

  if (normalizedValue === 'general_development') {
    return 'auto_worker';
  }

  return normalizedValue;
}

function buildNameKey(value: string) {
  return value.replace(/\s+/g, ' ').toLocaleLowerCase('ko-KR');
}

function buildContextTitleKey(value: string) {
  return value.replace(/\s+/g, ' ').toLocaleLowerCase('ko-KR');
}

function normalizeAutomationContext(record: Partial<AutomationTypeContextRecord>): AutomationTypeContextRecord | null {
  const title = normalizeText(record.title);
  const content = normalizeText(record.content);

  if (!title && !content) {
    return null;
  }

  const rawId = normalizeText(record.id);
  const normalizedId =
    rawId || `automation-context-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;

  return {
    id: normalizedId,
    title: title || 'Context',
    content,
    enabled: normalizeEnabled(record.enabled),
    defaultSelected: normalizeEnabled(record.defaultSelected),
    updatedAt: normalizeText(record.updatedAt) || new Date().toISOString(),
  };
}

function compareContextUpdatedAt(left: AutomationTypeContextRecord, right: AutomationTypeContextRecord) {
  const leftTime = Date.parse(left.updatedAt);
  const rightTime = Date.parse(right.updatedAt);

  if (Number.isFinite(leftTime) && Number.isFinite(rightTime) && leftTime !== rightTime) {
    return leftTime - rightTime;
  }

  return 0;
}

export function sanitizeAutomationContexts(items: Partial<AutomationTypeContextRecord>[] | null | undefined) {
  const byId = new Map<string, AutomationTypeContextRecord>();
  const bySemanticKey = new Map<string, AutomationTypeContextRecord>();

  (items ?? [])
    .map((item) => normalizeAutomationContext(item))
    .filter((item): item is AutomationTypeContextRecord => Boolean(item))
    .forEach((item) => {
      const currentById = byId.get(item.id);
      if (!currentById || compareContextUpdatedAt(currentById, item) <= 0) {
        byId.set(item.id, item);
      }
    });

  for (const item of byId.values()) {
    const semanticKey = buildContextTitleKey(item.title);
    const current = bySemanticKey.get(semanticKey);

    if (!current || compareContextUpdatedAt(current, item) <= 0) {
      bySemanticKey.set(semanticKey, item);
    }
  }

  return Array.from(bySemanticKey.values()).sort((left, right) => left.title.localeCompare(right.title, 'ko-KR'));
}

function normalizeAutomationType(record: Partial<AutomationTypeRecord>): AutomationTypeRecord | null {
  const name = normalizeText(record.name);

  if (!name) {
    return null;
  }

  const rawId = normalizeText(record.id);
  const normalizedId =
    rawId || `automation-type-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;

  return {
    id: normalizedId,
    name,
    description: normalizeText(record.description),
    contexts: sanitizeAutomationContexts(record.contexts),
    behaviorType: normalizeBehaviorType(record.behaviorType),
    enabled: normalizeEnabled(record.enabled),
    updatedAt: normalizeText(record.updatedAt) || new Date().toISOString(),
  };
}

function compareUpdatedAt(left: AutomationTypeRecord, right: AutomationTypeRecord) {
  const leftTime = Date.parse(left.updatedAt);
  const rightTime = Date.parse(right.updatedAt);

  if (Number.isFinite(leftTime) && Number.isFinite(rightTime) && leftTime !== rightTime) {
    return leftTime - rightTime;
  }

  return 0;
}

function dedupeAutomationTypes(items: AutomationTypeRecord[]) {
  const byId = new Map<string, AutomationTypeRecord>();
  const bySemanticKey = new Map<string, AutomationTypeRecord>();

  for (const item of items) {
    const currentById = byId.get(item.id);

    if (!currentById || compareUpdatedAt(currentById, item) <= 0) {
      byId.set(item.id, item);
    }
  }

  for (const item of byId.values()) {
    const semanticKey = `${item.behaviorType}:${buildNameKey(item.name)}`;
    const current = bySemanticKey.get(semanticKey);

    if (!current || compareUpdatedAt(current, item) <= 0) {
      bySemanticKey.set(semanticKey, item);
    }
  }

  return Array.from(bySemanticKey.values()).sort((left, right) => left.name.localeCompare(right.name, 'ko-KR'));
}

export function sanitizeAutomationTypes(items: Partial<AutomationTypeRecord>[] | null | undefined) {
  const normalized = (items ?? [])
    .map((item) => normalizeAutomationType(item))
    .filter((item): item is AutomationTypeRecord => Boolean(item));

  if (normalized.length === 0) {
    return DEFAULT_AUTOMATION_TYPES;
  }

  return dedupeAutomationTypes(normalized);
}

function mergeDefaultAutomationTypes(items: AutomationTypeRecord[]) {
  const byId = new Map(items.map((item) => [item.id, item] as const));

  for (const defaultItem of DEFAULT_AUTOMATION_TYPES) {
    const existingItem = byId.get(defaultItem.id);

    if (!existingItem) {
      byId.set(defaultItem.id, defaultItem);
      continue;
    }

    byId.set(defaultItem.id, {
      ...existingItem,
      name: defaultItem.name,
      description: existingItem.description || defaultItem.description,
      contexts: sanitizeAutomationContexts(existingItem.contexts?.length ? existingItem.contexts : defaultItem.contexts),
      behaviorType: defaultItem.behaviorType,
    });
  }

  return sanitizeAutomationTypes(Array.from(byId.values()));
}

function isSameAutomationTypeList(left: AutomationTypeRecord[], right: AutomationTypeRecord[]) {
  if (left.length !== right.length) {
    return false;
  }

  return left.every((item, index) => {
    const target = right[index];
    return (
      target &&
      item.id === target.id &&
      item.name === target.name &&
      item.description === target.description &&
      JSON.stringify(item.contexts) === JSON.stringify(target.contexts) &&
      item.behaviorType === target.behaviorType &&
      item.enabled === target.enabled &&
      item.updatedAt === target.updatedAt
    );
  });
}

async function ensureAutomationTypesTable() {
  const hasTable = await db.schema.hasTable(AUTOMATION_TYPES_TABLE);

  if (!hasTable) {
    await db.schema.createTable(AUTOMATION_TYPES_TABLE, (table) => {
      table.string('id').primary();
      table.string('name').notNullable();
      table.text('description').notNullable().defaultTo('');
      table.text('contexts_json').notNullable().defaultTo('[]');
      table.string('behavior_type').notNullable();
      table.boolean('enabled').notNullable().defaultTo(true);
      table.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
    });
    return;
  }

  const requiredColumns: Array<[string, (table: any) => void]> = [
    ['name', (table) => table.string('name').notNullable().defaultTo('')],
    ['description', (table) => table.text('description').notNullable().defaultTo('')],
    ['contexts_json', (table) => table.text('contexts_json').notNullable().defaultTo('[]')],
    ['behavior_type', (table) => table.string('behavior_type').notNullable().defaultTo('none')],
    ['enabled', (table) => table.boolean('enabled').notNullable().defaultTo(true)],
    ['updated_at', (table) => table.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(db.fn.now())],
  ];

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

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

function normalizeConfigRecord(value: unknown) {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return {} as Record<string, unknown>;
  }

  return value as Record<string, unknown>;
}

function parseContextsFromRow(row: Record<string, unknown>) {
  const rawValue = row.contexts_json;

  if (typeof rawValue !== 'string') {
    return [];
  }

  try {
    const parsed = JSON.parse(rawValue);
    return Array.isArray(parsed) ? parsed : [];
  } catch {
    return [];
  }
}

function toAutomationTypeRecord(row: Record<string, unknown>) {
  return normalizeAutomationType({
    id: typeof row.id === 'string' ? row.id : undefined,
    name: typeof row.name === 'string' ? row.name : undefined,
    description: typeof row.description === 'string' ? row.description : undefined,
    contexts: parseContextsFromRow(row),
    behaviorType: normalizeLegacyAutomationBehaviorType(row.behavior_type) as AutomationBehaviorType,
    enabled: normalizeEnabled(row.enabled),
    updatedAt: typeof row.updated_at === 'string' ? row.updated_at : undefined,
  });
}

async function seedAutomationTypesFromLegacyConfig() {
  const config = normalizeConfigRecord(await getAppConfig());
  const raw = config[AUTOMATION_TYPES_CONFIG_KEY];

  if (!Array.isArray(raw) || raw.length === 0) {
    return mergeDefaultAutomationTypes(DEFAULT_AUTOMATION_TYPES);
  }

  const legacyItems = mergeDefaultAutomationTypes(
    sanitizeAutomationTypes(raw as Partial<AutomationTypeRecord>[]),
  );
  await replaceAutomationTypesInTable(legacyItems);
  return legacyItems;
}

async function readAutomationTypesFromTable() {
  await ensureAutomationTypesTable();

  const rows = await db(AUTOMATION_TYPES_TABLE)
    .select('id', 'name', 'description', 'contexts_json', 'behavior_type', 'enabled', 'updated_at')
    .orderBy('name', 'asc');

  const savedItems = rows
    .map((row) => toAutomationTypeRecord(row as Record<string, unknown>))
    .filter((item): item is AutomationTypeRecord => Boolean(item));

  return sanitizeAutomationTypes(savedItems);
}

async function replaceAutomationTypesInTable(items: AutomationTypeRecord[]) {
  await ensureAutomationTypesTable();

  const nextItems = sanitizeAutomationTypes(items);

  await db.transaction(async (trx) => {
    await trx(AUTOMATION_TYPES_TABLE).del();

    await trx(AUTOMATION_TYPES_TABLE).insert(
      nextItems.map((item) => ({
        id: item.id,
        name: item.name,
        description: item.description,
        contexts_json: JSON.stringify(item.contexts ?? []),
        behavior_type: item.behaviorType,
        enabled: item.enabled,
        updated_at: item.updatedAt,
      })),
    );
  });

  return nextItems;
}

export async function getAutomationTypesConfig() {
  const savedAutomationTypes = await readAutomationTypesFromTable();
  const automationTypes = mergeDefaultAutomationTypes(savedAutomationTypes);

  if (automationTypes.length === 0 || automationTypes === DEFAULT_AUTOMATION_TYPES) {
    return seedAutomationTypesFromLegacyConfig();
  }

  if (!isSameAutomationTypeList(savedAutomationTypes, automationTypes)) {
    await replaceAutomationTypesInTable(automationTypes);
  }

  return automationTypes;
}

export async function upsertAutomationTypesConfig(items: unknown[]) {
  const nextAutomationTypes = mergeDefaultAutomationTypes(
    sanitizeAutomationTypes(Array.isArray(items) ? (items as Partial<AutomationTypeRecord>[]) : []),
  );
  return replaceAutomationTypesInTable(nextAutomationTypes);
}

export async function resolveAutomationType(input: unknown) {
  const requestedId = normalizeLegacyAutomationTypeId(input);
  const automationTypes = await getAutomationTypesConfig();
  const matched = automationTypes.find((item) => item.id === requestedId);

  if (matched) {
    return matched;
  }

  const matchedByBehavior = automationTypes.find((item) => item.behaviorType === normalizeBehaviorType(requestedId));

  if (matchedByBehavior) {
    return matchedByBehavior;
  }

  return (
    DEFAULT_AUTOMATION_TYPES.find((item) => item.id === normalizeBehaviorType(requestedId)) ??
    DEFAULT_AUTOMATION_TYPES[0]
  );
}

export function resolveStoredAutomationTypeId(row: Record<string, unknown>) {
  const automationTypeId = normalizeText(row.automation_type_id);

  if (automationTypeId) {
    return normalizeLegacyAutomationTypeId(automationTypeId);
  }

  return normalizeLegacyAutomationTypeId(row.automation_type) || 'none';
}

export function normalizeAutomationContextSelection(value: unknown) {
  const rawValues = Array.isArray(value)
    ? value
    : typeof value === 'string'
      ? value
          .split(',')
          .map((item) => item.trim())
          .filter(Boolean)
      : [];

  return [...new Set(rawValues.map((item) => normalizeText(String(item))).filter(Boolean))];
}

export function resolveAutomationTypeContexts(
  automationType: Pick<AutomationTypeRecord, 'contexts'> | null | undefined,
  selectedContextIds?: unknown,
) {
  const contexts = sanitizeAutomationContexts(automationType?.contexts);
  const requestedIds = normalizeAutomationContextSelection(selectedContextIds);

  if (requestedIds.length === 0 && Array.isArray(selectedContextIds)) {
    return [];
  }

  if (requestedIds.length === 0) {
    return contexts.filter((item) => item.enabled && item.defaultSelected);
  }

  const requestedIdSet = new Set(requestedIds);
  return contexts.filter((item) => requestedIdSet.has(item.id));
}
