import { db } from '../db/client.js';
import {
  CHAT_CONVERSATION_TABLE,
  ensureChatConversationTables,
} from './chat-room-service.js';

const CHAT_SHARE_TOKEN_ROOM_MAP_TABLE = 'chat_share_token_room_maps';

export type ChatShareTokenRoomMapItem = {
  tokenId: string;
  sessionId: string;
  rootRequestId: string;
  isDefault: boolean;
  sortOrder: number;
  createdByClientId: string | null;
  title: string;
  requestBadgeLabel: string | null;
  chatTypeId: string | null;
  lastChatTypeId: string | null;
  contextLabel: string | null;
  contextDescription: string | null;
  notifyOffline: boolean;
  linkContext: ChatShareRoomLinkContext | null;
  createdAt: string | null;
  updatedAt: string | null;
  conversationUpdatedAt: string | null;
};

export type ChatShareRoomLinkContext = {
  kind: 'linked-session';
  sourceSessionId: string;
  sourceRequestId: string;
  sourceTitle: string | null;
  sourceRequestPreview: string | null;
  sourceChatTypeLabel: string | null;
  linkedAt: string | null;
};

function normalizeOptionalText(value: unknown) {
  if (typeof value !== 'string') {
    return null;
  }

  const normalized = value.trim();
  return normalized || null;
}

function normalizeRequiredText(value: unknown) {
  if (typeof value !== 'string') {
    return '';
  }

  return value.trim();
}

function normalizeBoolean(value: unknown) {
  return value === true;
}

function normalizeInteger(value: unknown, fallback = 0) {
  const parsed = Number(value);
  if (!Number.isFinite(parsed)) {
    return fallback;
  }

  return Math.trunc(parsed);
}

function normalizeDateTime(value: unknown) {
  if (value == null) {
    return null;
  }

  if (value instanceof Date) {
    return Number.isNaN(value.getTime()) ? null : value.toISOString();
  }

  if (typeof value === 'string') {
    const normalized = value.trim();
    return normalized || null;
  }

  return null;
}

function parseChatShareRoomLinkContext(value: unknown) {
  if (typeof value !== 'string') {
    return null;
  }

  const normalized = value.trim();

  if (!normalized) {
    return null;
  }

  try {
    const parsed = JSON.parse(normalized) as Record<string, unknown>;

    if (parsed.kind !== 'linked-session') {
      return null;
    }

    const sourceSessionId = normalizeRequiredText(parsed.sourceSessionId);
    const sourceRequestId = normalizeRequiredText(parsed.sourceRequestId);

    if (!sourceSessionId || !sourceRequestId) {
      return null;
    }

    return {
      kind: 'linked-session',
      sourceSessionId,
      sourceRequestId,
      sourceTitle: normalizeOptionalText(parsed.sourceTitle),
      sourceRequestPreview: normalizeOptionalText(parsed.sourceRequestPreview),
      sourceChatTypeLabel: normalizeOptionalText(parsed.sourceChatTypeLabel),
      linkedAt: normalizeDateTime(parsed.linkedAt),
    } satisfies ChatShareRoomLinkContext;
  } catch {
    return null;
  }
}

function stringifyChatShareRoomLinkContext(value: ChatShareRoomLinkContext | null | undefined) {
  if (!value) {
    return null;
  }

  if (value.kind !== 'linked-session') {
    return null;
  }

  const sourceSessionId = normalizeRequiredText(value.sourceSessionId);
  const sourceRequestId = normalizeRequiredText(value.sourceRequestId);

  if (!sourceSessionId || !sourceRequestId) {
    return null;
  }

  return JSON.stringify({
    kind: 'linked-session',
    sourceSessionId,
    sourceRequestId,
    sourceTitle: normalizeOptionalText(value.sourceTitle),
    sourceRequestPreview: normalizeOptionalText(value.sourceRequestPreview),
    sourceChatTypeLabel: normalizeOptionalText(value.sourceChatTypeLabel),
    linkedAt: normalizeDateTime(value.linkedAt),
  });
}

function mapChatShareTokenRoomRow(row: Record<string, unknown>): ChatShareTokenRoomMapItem {
  return {
    tokenId: normalizeRequiredText(row.shared_resource_token_id),
    sessionId: normalizeRequiredText(row.session_id),
    rootRequestId: normalizeRequiredText(row.root_request_id),
    isDefault: normalizeBoolean(row.is_default),
    sortOrder: normalizeInteger(row.sort_order),
    createdByClientId: normalizeOptionalText(row.created_by_client_id),
    title: normalizeRequiredText(row.title) || '공유 채팅방',
    requestBadgeLabel: normalizeOptionalText(row.request_badge_label),
    chatTypeId: normalizeOptionalText(row.chat_type_id),
    lastChatTypeId: normalizeOptionalText(row.last_chat_type_id),
    contextLabel: normalizeOptionalText(row.context_label),
    contextDescription: normalizeOptionalText(row.context_description),
    notifyOffline: normalizeBoolean(row.notify_offline),
    linkContext: parseChatShareRoomLinkContext(row.link_context_json),
    createdAt: normalizeDateTime(row.created_at),
    updatedAt: normalizeDateTime(row.updated_at),
    conversationUpdatedAt: normalizeDateTime(row.conversation_updated_at),
  };
}

export async function ensureChatShareTokenRoomMapTable() {
  await ensureChatConversationTables();

  const hasTable = await db.schema.hasTable(CHAT_SHARE_TOKEN_ROOM_MAP_TABLE);

  if (!hasTable) {
    await db.schema.createTable(CHAT_SHARE_TOKEN_ROOM_MAP_TABLE, (table) => {
      table.increments('id').primary();
      table.string('shared_resource_token_id', 120).notNullable().index();
      table.string('session_id', 120).notNullable().index();
      table.string('root_request_id', 120).notNullable();
      table.boolean('is_default').notNullable().defaultTo(false);
      table.integer('sort_order').notNullable().defaultTo(0);
      table.string('created_by_client_id', 120).nullable();
      table.timestamp('archived_at', { useTz: true }).nullable().index();
      table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
      table.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
      table.unique(['shared_resource_token_id', 'session_id']);
    });
  }

  const requiredColumns: Array<[string, (table: any) => void]> = [
    ['shared_resource_token_id', (table) => table.string('shared_resource_token_id', 120).notNullable().index()],
    ['session_id', (table) => table.string('session_id', 120).notNullable().index()],
    ['root_request_id', (table) => table.string('root_request_id', 120).notNullable().defaultTo('')],
    ['is_default', (table) => table.boolean('is_default').notNullable().defaultTo(false)],
    ['sort_order', (table) => table.integer('sort_order').notNullable().defaultTo(0)],
    ['created_by_client_id', (table) => table.string('created_by_client_id', 120).nullable()],
    ['link_context_json', (table) => table.text('link_context_json').nullable()],
    ['archived_at', (table) => table.timestamp('archived_at', { useTz: true }).nullable().index()],
    ['created_at', (table) => table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(db.fn.now())],
    ['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(CHAT_SHARE_TOKEN_ROOM_MAP_TABLE, columnName);

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

export async function listChatShareTokenRoomMaps(tokenId: string) {
  const normalizedTokenId = tokenId.trim();

  if (!normalizedTokenId) {
    return [] as ChatShareTokenRoomMapItem[];
  }

  await ensureChatShareTokenRoomMapTable();

  const rows = await db(`${CHAT_SHARE_TOKEN_ROOM_MAP_TABLE} as room_map`)
    .leftJoin(`${CHAT_CONVERSATION_TABLE} as conversation`, 'conversation.session_id', 'room_map.session_id')
    .select(
      'room_map.shared_resource_token_id',
      'room_map.session_id',
      'room_map.root_request_id',
      'room_map.is_default',
      'room_map.sort_order',
      'room_map.created_by_client_id',
      'room_map.created_at',
      'room_map.updated_at',
      'conversation.title',
      'conversation.request_badge_label',
      'conversation.chat_type_id',
      'conversation.last_chat_type_id',
      'conversation.context_label',
      'conversation.context_description',
      'conversation.notify_offline',
      'room_map.link_context_json',
      'conversation.updated_at as conversation_updated_at',
    )
    .where({ 'room_map.shared_resource_token_id': normalizedTokenId })
    .whereNull('room_map.archived_at')
    .orderBy('room_map.is_default', 'desc')
    .orderBy('room_map.sort_order', 'asc')
    .orderBy('room_map.created_at', 'asc');

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

export async function getChatShareTokenRoomMap(tokenId: string, sessionId: string) {
  const normalizedTokenId = tokenId.trim();
  const normalizedSessionId = sessionId.trim();

  if (!normalizedTokenId || !normalizedSessionId) {
    return null;
  }

  const rooms = await listChatShareTokenRoomMaps(normalizedTokenId);
  return rooms.find((item) => item.sessionId === normalizedSessionId) ?? null;
}

export async function upsertChatShareTokenRoomMap(args: {
  tokenId: string;
  sessionId: string;
  rootRequestId: string;
  isDefault?: boolean;
  sortOrder?: number | null;
  createdByClientId?: string | null;
  linkContext?: ChatShareRoomLinkContext | null;
}) {
  const normalizedTokenId = args.tokenId.trim();
  const normalizedSessionId = args.sessionId.trim();
  const normalizedRootRequestId = args.rootRequestId.trim();

  if (!normalizedTokenId || !normalizedSessionId || !normalizedRootRequestId) {
    return null;
  }

  await ensureChatShareTokenRoomMapTable();

  await db.transaction(async (trx) => {
    const current = await trx(CHAT_SHARE_TOKEN_ROOM_MAP_TABLE)
      .where({
        shared_resource_token_id: normalizedTokenId,
        session_id: normalizedSessionId,
      })
      .whereNull('archived_at')
      .first();

    const maxSortOrderRow = await trx(CHAT_SHARE_TOKEN_ROOM_MAP_TABLE)
      .where({ shared_resource_token_id: normalizedTokenId })
      .whereNull('archived_at')
      .max<{ max_sort_order?: number | string | null }>('sort_order as max_sort_order')
      .first();
    const nextSortOrder = args.sortOrder != null
      ? Math.max(0, Math.trunc(Number(args.sortOrder) || 0))
      : Math.max(0, normalizeInteger(maxSortOrderRow?.max_sort_order) + (current ? 0 : 1));

    if (args.isDefault === true) {
      await trx(CHAT_SHARE_TOKEN_ROOM_MAP_TABLE)
        .where({ shared_resource_token_id: normalizedTokenId })
        .whereNull('archived_at')
        .update({
          is_default: false,
          updated_at: db.fn.now(),
        });
    }

    const payload = {
      shared_resource_token_id: normalizedTokenId,
      session_id: normalizedSessionId,
      root_request_id: normalizedRootRequestId,
      is_default: args.isDefault === true,
      sort_order: nextSortOrder,
      created_by_client_id: normalizeOptionalText(args.createdByClientId),
      link_context_json:
        args.linkContext === undefined
          ? (current?.link_context_json ?? null)
          : stringifyChatShareRoomLinkContext(args.linkContext),
      archived_at: null,
      updated_at: db.fn.now(),
    };

    if (current) {
      await trx(CHAT_SHARE_TOKEN_ROOM_MAP_TABLE)
        .where({
          shared_resource_token_id: normalizedTokenId,
          session_id: normalizedSessionId,
        })
        .update(payload);
      return;
    }

    await trx(CHAT_SHARE_TOKEN_ROOM_MAP_TABLE).insert({
      ...payload,
      created_at: db.fn.now(),
    });
  });

  return getChatShareTokenRoomMap(normalizedTokenId, normalizedSessionId);
}

export async function ensureDefaultChatShareTokenRoomMap(args: {
  tokenId: string;
  sessionId: string;
  rootRequestId: string;
  createdByClientId?: string | null;
}) {
  const normalizedTokenId = args.tokenId.trim();
  const normalizedSessionId = args.sessionId.trim();
  const normalizedRootRequestId = args.rootRequestId.trim();

  if (!normalizedTokenId || !normalizedSessionId || !normalizedRootRequestId) {
    return [];
  }

  const existing = await getChatShareTokenRoomMap(normalizedTokenId, normalizedSessionId);

  if (!existing) {
    await upsertChatShareTokenRoomMap({
      tokenId: normalizedTokenId,
      sessionId: normalizedSessionId,
      rootRequestId: normalizedRootRequestId,
      isDefault: true,
      createdByClientId: args.createdByClientId ?? null,
    });
  }

  const rooms = await listChatShareTokenRoomMaps(normalizedTokenId);

  if (rooms.some((item) => item.isDefault)) {
    return rooms;
  }

  await upsertChatShareTokenRoomMap({
    tokenId: normalizedTokenId,
    sessionId: normalizedSessionId,
    rootRequestId: normalizedRootRequestId,
    isDefault: true,
    createdByClientId: args.createdByClientId ?? null,
  });

  return listChatShareTokenRoomMaps(normalizedTokenId);
}

export async function resolveChatShareTokenRoomSessionIds(tokenId: string) {
  const rooms = await listChatShareTokenRoomMaps(tokenId);
  return rooms.map((item) => item.sessionId).filter(Boolean);
}

export async function archiveChatShareTokenRoomMap(tokenId: string, sessionId: string) {
  const normalizedTokenId = tokenId.trim();
  const normalizedSessionId = sessionId.trim();

  if (!normalizedTokenId || !normalizedSessionId) {
    return {
      archived: false,
      archivedRoom: null,
      nextDefaultRoom: null,
    } as const;
  }

  await ensureChatShareTokenRoomMapTable();

  return db.transaction(async (trx) => {
    const current = await trx(`${CHAT_SHARE_TOKEN_ROOM_MAP_TABLE} as room_map`)
      .leftJoin(`${CHAT_CONVERSATION_TABLE} as conversation`, 'conversation.session_id', 'room_map.session_id')
      .select(
        'room_map.shared_resource_token_id',
        'room_map.session_id',
        'room_map.root_request_id',
        'room_map.is_default',
        'room_map.sort_order',
        'room_map.created_by_client_id',
        'room_map.created_at',
        'room_map.updated_at',
        'conversation.title',
        'conversation.request_badge_label',
        'conversation.chat_type_id',
        'conversation.last_chat_type_id',
        'conversation.context_label',
        'conversation.context_description',
        'conversation.notify_offline',
        'conversation.updated_at as conversation_updated_at',
      )
      .where({
        'room_map.shared_resource_token_id': normalizedTokenId,
        'room_map.session_id': normalizedSessionId,
      })
      .whereNull('room_map.archived_at')
      .first();

    if (!current) {
      return {
        archived: false,
        archivedRoom: null,
        nextDefaultRoom: null,
      } as const;
    }

    await trx(CHAT_SHARE_TOKEN_ROOM_MAP_TABLE)
      .where({
        shared_resource_token_id: normalizedTokenId,
        session_id: normalizedSessionId,
      })
      .whereNull('archived_at')
      .update({
        archived_at: db.fn.now(),
        updated_at: db.fn.now(),
      });

    let nextDefaultRoom: ChatShareTokenRoomMapItem | null = null;

    if (current.is_default) {
      const nextDefaultRow = await trx(`${CHAT_SHARE_TOKEN_ROOM_MAP_TABLE} as room_map`)
        .leftJoin(`${CHAT_CONVERSATION_TABLE} as conversation`, 'conversation.session_id', 'room_map.session_id')
        .select(
          'room_map.shared_resource_token_id',
          'room_map.session_id',
          'room_map.root_request_id',
          'room_map.is_default',
          'room_map.sort_order',
          'room_map.created_by_client_id',
          'room_map.created_at',
          'room_map.updated_at',
          'conversation.title',
          'conversation.request_badge_label',
          'conversation.chat_type_id',
          'conversation.last_chat_type_id',
          'conversation.context_label',
          'conversation.context_description',
          'conversation.notify_offline',
          'conversation.updated_at as conversation_updated_at',
        )
        .where({ 'room_map.shared_resource_token_id': normalizedTokenId })
        .whereNull('room_map.archived_at')
        .orderBy('room_map.sort_order', 'asc')
        .orderBy('room_map.created_at', 'asc')
        .first();

      if (nextDefaultRow) {
        await trx(CHAT_SHARE_TOKEN_ROOM_MAP_TABLE)
          .where({
            shared_resource_token_id: normalizedTokenId,
            session_id: nextDefaultRow.session_id,
          })
          .whereNull('archived_at')
          .update({
            is_default: true,
            updated_at: db.fn.now(),
          });

        nextDefaultRoom = mapChatShareTokenRoomRow({
          ...nextDefaultRow,
          is_default: true,
        });
      }
    }

    return {
      archived: true,
      archivedRoom: mapChatShareTokenRoomRow(current),
      nextDefaultRoom,
    } as const;
  });
}
