import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import { env } from '../config/env.js';
import { getSharedResourceTokenDetailBySharePath } from '../services/shared-resource-token-service.js';
import {
  getChatContextSettingsConfig,
  getAppConfigSnapshot,
  getChatTypesConfig,
  normalizeAppConfigSnapshot,
  upsertAppConfig,
  upsertChatContextSettingsConfig,
  upsertChatTypesConfig,
} from '../services/app-config-service.js';
import {
  getAutomationContextsConfig,
  upsertAutomationContextsConfig,
} from '../services/automation-context-config-service.js';
import { getAutomationTypesConfig, upsertAutomationTypesConfig } from '../services/automation-type-config-service.js';
import {
  getTokenSettingsConfig,
  getTokenSettingById,
  upsertTokenSettingsConfig,
  type TokenSettingRecord,
} from '../services/token-setting-config-service.js';
import { listTokenSettingActivities } from '../services/token-setting-activity-service.js';
import { extractRequestAuditContext } from '../utils/request-audit.js';

const CHAT_SHARE_PATH_PREFIX = '/chat/share/';

function getRequestAccessToken(request: { headers: Record<string, string | string[] | undefined> }) {
  const tokenHeader = request.headers['x-access-token'];
  return Array.isArray(tokenHeader) ? tokenHeader[0]?.trim() ?? '' : String(tokenHeader ?? '').trim();
}

function getRequestChatShareToken(request: { headers: Record<string, string | string[] | undefined> }) {
  const tokenHeader = request.headers['x-chat-share-token'];
  return Array.isArray(tokenHeader) ? tokenHeader[0]?.trim() ?? '' : String(tokenHeader ?? '').trim();
}

function resolveChatSharePath(token: string) {
  return `${CHAT_SHARE_PATH_PREFIX}${encodeURIComponent(token)}`;
}

type TokenSettingsAccessContext =
  | { scope: 'full' }
  | { scope: 'shared'; tokenSetting: TokenSettingRecord };

type AppConfigAccessContext =
  | { scope: 'full' }
  | { scope: 'shared' };

async function resolveTokenSettingsAccessContext(
  request: { headers: Record<string, string | string[] | undefined> },
) {
  if (getRequestAccessToken(request) === env.SERVER_COMMAND_ACCESS_TOKEN) {
    return { scope: 'full' } satisfies TokenSettingsAccessContext;
  }

  const shareToken = getRequestChatShareToken(request);
  if (!shareToken) {
    return null;
  }

  const managedResource = await getSharedResourceTokenDetailBySharePath(resolveChatSharePath(shareToken));
  if (!managedResource || managedResource.token.enabled === false || managedResource.token.revokedAt) {
    return null;
  }

  if (managedResource.token.resourceType === 'chat-share') {
    return null;
  }

  const allowedAppIds = managedResource.token.allowedAppIds ?? [];
  const normalizedAllowedAppIds = new Set(allowedAppIds.map((item) => item.trim().toLowerCase()).filter(Boolean));
  const hasManagePermission = managedResource.token.permissions.includes('manage');
  const canOpenTokenSetting = normalizedAllowedAppIds.has('token-setting');
  const tokenSettingId = managedResource.token.tokenSettingId?.trim() ?? '';

  if (!hasManagePermission || !canOpenTokenSetting || !tokenSettingId) {
    return null;
  }

  const tokenSetting = await getTokenSettingById(tokenSettingId);
  if (!tokenSetting) {
    return null;
  }

  return { scope: 'shared', tokenSetting } satisfies TokenSettingsAccessContext;
}

async function resolveAppConfigAccessContext(
  request: { headers: Record<string, string | string[] | undefined> },
) {
  if (getRequestAccessToken(request) === env.SERVER_COMMAND_ACCESS_TOKEN) {
    return { scope: 'full' } satisfies AppConfigAccessContext;
  }

  const shareToken = getRequestChatShareToken(request);
  if (!shareToken) {
    return null;
  }

  const managedResource = await getSharedResourceTokenDetailBySharePath(resolveChatSharePath(shareToken));
  if (!managedResource || managedResource.token.enabled === false || managedResource.token.revokedAt) {
    return null;
  }

  if (managedResource.token.resourceType === 'chat-share') {
    return null;
  }

  const allowedAppIds = managedResource.token.allowedAppIds ?? [];
  const normalizedAllowedAppIds = new Set(allowedAppIds.map((item) => item.trim().toLowerCase()).filter(Boolean));
  const hasManagePermission = managedResource.token.permissions.includes('manage');
  const canOpenAppSettings = normalizedAllowedAppIds.has('app-settings');

  if (!hasManagePermission || !canOpenAppSettings) {
    return null;
  }

  return { scope: 'shared' } satisfies AppConfigAccessContext;
}

function sendTokenSettingsAccessDenied(
  reply: { code: (statusCode: number) => { send: (payload: { message: string }) => unknown } },
) {
  reply.code(403).send({
    message: '권한 토큰 또는 토큰관리 관리 권한이 있는 공유 링크에서만 토큰 설정을 사용할 수 있습니다.',
  });
}

function sendAppConfigAccessDenied(
  reply: { code: (statusCode: number) => { send: (payload: { message: string }) => unknown } },
) {
  reply.code(403).send({
    message: '권한 토큰 또는 앱 설정 관리 권한이 있는 공유 링크에서만 앱 설정을 사용할 수 있습니다.',
  });
}

function getRequestAppOrigin(request: { headers: Record<string, string | string[] | undefined> }) {
  const rawAppOrigin = request.headers['x-app-origin'];
  const appOrigin = Array.isArray(rawAppOrigin) ? rawAppOrigin[0] : rawAppOrigin;

  if (appOrigin?.trim()) {
    return appOrigin.trim();
  }

  const rawOrigin = request.headers.origin;
  const origin = Array.isArray(rawOrigin) ? rawOrigin[0] : rawOrigin;
  return origin?.trim() ?? '';
}

function getRequestAppDomain(request: { headers: Record<string, string | string[] | undefined> }) {
  const rawAppDomain = request.headers['x-app-domain'];
  const appDomain = Array.isArray(rawAppDomain) ? rawAppDomain[0] : rawAppDomain;

  if (appDomain?.trim()) {
    return appDomain.trim();
  }

  const appOrigin = getRequestAppOrigin(request);

  if (!appOrigin) {
    return '';
  }

  try {
    return new URL(appOrigin).hostname;
  } catch {
    return '';
  }
}

export async function registerAppConfigRoutes(app: FastifyInstance) {
  app.get('/api/app-config', async (request, reply) => {
    const accessContext = await resolveAppConfigAccessContext(request);
    const hasShareToken = Boolean(getRequestChatShareToken(request));

    if (hasShareToken && !accessContext) {
      sendAppConfigAccessDenied(reply);
      return;
    }

    const appOrigin = getRequestAppOrigin(request);
    const config = await getAppConfigSnapshot(appOrigin);

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

  app.get('/api/chat-types', async (request) => {
    const chatTypeConfig = await getChatTypesConfig(getRequestAppOrigin(request));

    return {
      ok: true,
      ...chatTypeConfig,
    };
  });

  app.get('/api/chat-context-settings', async (request) => {
    const settings = await getChatContextSettingsConfig(getRequestAppOrigin(request));

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

  app.get('/api/automation-types', async () => {
    const automationTypes = await getAutomationTypesConfig();

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

  app.get('/api/automation-contexts', async () => {
    const automationContexts = await getAutomationContextsConfig();

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

  app.get('/api/token-settings', async (request, reply) => {
    const accessContext = await resolveTokenSettingsAccessContext(request);
    if (!accessContext) {
      sendTokenSettingsAccessDenied(reply);
      return;
    }

    const tokenSettings =
      accessContext.scope === 'full' ? await getTokenSettingsConfig() : [accessContext.tokenSetting];

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

  app.put('/api/chat-types', async (request, reply) => {
    try {
      let payload: unknown = request.body ?? {};

      if (typeof payload === 'string') {
        try {
          payload = JSON.parse(payload);
        } catch {
          payload = {};
        }
      }

      const parsed = z
        .object({
          chatTypes: z.array(z.unknown()).optional(),
        })
        .parse(payload ?? {});

      const appOrigin = getRequestAppOrigin(request);
      const appDomain = getRequestAppDomain(request);
      const targetChatTypes = parsed.chatTypes ?? [];
      const savedChatTypeConfig = await upsertChatTypesConfig(targetChatTypes, appOrigin, appDomain);

      return {
        ok: true,
        ...savedChatTypeConfig,
      };
    } catch (error) {
      return reply.code(409).send({
        message: error instanceof Error ? error.message : '채팅유형 저장에 실패했습니다.',
      });
    }
  });

  app.put('/api/chat-context-settings', async (request, reply) => {
    try {
      let payload: unknown = request.body ?? {};

      if (typeof payload === 'string') {
        try {
          payload = JSON.parse(payload);
        } catch {
          payload = {};
        }
      }

      const parsed = z.object({
        settings: z.unknown(),
      }).parse(payload ?? {});

      const appOrigin = getRequestAppOrigin(request);
      const appDomain = getRequestAppDomain(request);
      const savedSettings = await upsertChatContextSettingsConfig(parsed.settings, appOrigin, appDomain);

      return {
        ok: true,
        settings: savedSettings,
      };
    } catch (error) {
      return reply.code(409).send({
        message: error instanceof Error ? error.message : '채팅 Context 설정 저장에 실패했습니다.',
      });
    }
  });

  app.put('/api/automation-types', async (request, reply) => {
    try {
      let payload: unknown = request.body ?? {};

      if (typeof payload === 'string') {
        try {
          payload = JSON.parse(payload);
        } catch {
          payload = {};
        }
      }

      const parsed = z.object({
        automationTypes: z.array(z.unknown()),
      }).parse(payload ?? {});

      const savedAutomationTypes = await upsertAutomationTypesConfig(parsed.automationTypes);

      return {
        ok: true,
        automationTypes: savedAutomationTypes,
      };
    } catch (error) {
      return reply.code(409).send({
        message: error instanceof Error ? error.message : '자동화 처리 유형 저장에 실패했습니다.',
      });
    }
  });

  app.put('/api/automation-contexts', async (request, reply) => {
    try {
      let payload: unknown = request.body ?? {};

      if (typeof payload === 'string') {
        try {
          payload = JSON.parse(payload);
        } catch {
          payload = {};
        }
      }

      const parsed = z.object({
        automationContexts: z.array(z.unknown()),
      }).parse(payload ?? {});

      const savedAutomationContexts = await upsertAutomationContextsConfig(parsed.automationContexts);

      return {
        ok: true,
        automationContexts: savedAutomationContexts,
      };
    } catch (error) {
      return reply.code(409).send({
        message: error instanceof Error ? error.message : '자동화 Context 저장에 실패했습니다.',
      });
    }
  });

  app.put('/api/token-settings', async (request, reply) => {
    const accessContext = await resolveTokenSettingsAccessContext(request);
    if (!accessContext) {
      sendTokenSettingsAccessDenied(reply);
      return;
    }

    try {
      let payload: unknown = request.body ?? {};

      if (typeof payload === 'string') {
        try {
          payload = JSON.parse(payload);
        } catch {
          payload = {};
        }
      }

      const parsed = z.object({
        tokenSettings: z.array(z.unknown()),
      }).parse(payload ?? {});

      const nextTokenSettingsInput = parsed.tokenSettings as Partial<TokenSettingRecord>[];
      const savedTokenSettings =
        accessContext.scope === 'full'
          ? await upsertTokenSettingsConfig(nextTokenSettingsInput, {
              actorLabel: 'manager',
              audit: extractRequestAuditContext(request),
            })
          : await (async () => {
              const authorizedSettingId = accessContext.tokenSetting.id;
              const requestedSetting = nextTokenSettingsInput.find(
                (item) => typeof item?.id === 'string' && item.id.trim().toLowerCase() === authorizedSettingId,
              );

              if (!requestedSetting) {
                throw new Error('공유 링크에서는 현재 연결된 토큰 설정만 저장할 수 있습니다.');
              }

              const currentTokenSettings = await getTokenSettingsConfig();
              const nextTokenSettings = currentTokenSettings.map((item) =>
                item.id === authorizedSettingId ? { ...requestedSetting, id: authorizedSettingId } : item,
              );
              return upsertTokenSettingsConfig(nextTokenSettings, {
                actorLabel: 'shared-link-manager',
                audit: extractRequestAuditContext(request),
              });
            })();

      return {
        ok: true,
        tokenSettings: accessContext.scope === 'full' ? savedTokenSettings : savedTokenSettings.filter((item) => item.id === accessContext.tokenSetting.id),
      };
    } catch (error) {
      return reply.code(409).send({
        message: error instanceof Error ? error.message : '토큰 설정 저장에 실패했습니다.',
      });
    }
  });

  app.get('/api/token-settings/:settingId/activities', async (request, reply) => {
    const accessContext = await resolveTokenSettingsAccessContext(request);
    if (!accessContext) {
      sendTokenSettingsAccessDenied(reply);
      return;
    }

    const settingId = z.string().trim().min(1).parse((request.params as { settingId: string }).settingId);
    if (accessContext.scope === 'shared' && accessContext.tokenSetting.id !== settingId) {
      return reply.code(403).send({
        message: '현재 공유 링크에서는 연결된 토큰 설정 이력만 볼 수 있습니다.',
      });
    }

    return {
      ok: true,
      activities: await listTokenSettingActivities(settingId),
    };
  });

  app.put('/api/app-config', async (request, reply) => {
    const accessContext = await resolveAppConfigAccessContext(request);
    if (!accessContext) {
      sendAppConfigAccessDenied(reply);
      return;
    }

    try {
      let payload: unknown = request.body ?? {};

      if (typeof payload === 'string') {
        try {
          payload = JSON.parse(payload);
        } catch {
          payload = {};
        }
      }

      if (!payload || typeof payload !== 'object' || !('config' in payload)) {
        throw new Error('저장할 설정 값이 비어 있습니다.');
      }

      const config = (payload as { config: unknown }).config;

      if (!config || typeof config !== 'object') {
        throw new Error('설정 값 형식이 올바르지 않습니다.');
      }

      const appOrigin = getRequestAppOrigin(request);
      const appDomain = getRequestAppDomain(request);
      const savedConfig = await upsertAppConfig(config as Record<string, unknown>, appOrigin, appDomain);

      return {
        ok: true,
        config: normalizeAppConfigSnapshot(savedConfig),
      };
    } catch (error) {
      return reply.code(409).send({
        message: error instanceof Error ? error.message : '앱 설정 저장에 실패했습니다.',
      });
    }
  });
}
