import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import { z } from 'zod';
import { env } from '../config/env.js';
import { getSharedResourceTokenDetailBySharePath } from '../services/shared-resource-token-service.js';
import {
  deployTestServerCommand,
  deployWorkServerCommand,
  listServerCommands,
  readWorkServerDeploymentState,
  restartServerCommand,
  serverCommandKeys,
} from '../services/server-command-service.js';
import { readTestServerDeploymentState } from '../services/test-server-deployment-service.js';
import {
  cancelServerRestartReservation,
  confirmServerRestartReservation,
  getRestartReservationWorkloadSummary,
  requestImmediateRestartRecovery,
  getServerRestartReservation,
  scheduleServerRestartReservation,
} from '../services/server-restart-reservation-service.js';

const serverCommandParamSchema = z.object({
  key: z.enum(serverCommandKeys),
});

const restartReservationBodySchema = z.object({
  target: z.enum(['all', 'test', 'work-server']).optional(),
  autoExecuteDelaySeconds: z.number().int().min(1).max(300).optional(),
});
const CHAT_SHARE_PATH_PREFIX = '/chat/share/';

function getImmediateRestartBlockInfo(
  key: z.infer<typeof serverCommandParamSchema>['key'],
  workloadSummary: Awaited<ReturnType<typeof getRestartReservationWorkloadSummary>>,
) {
  const codexPendingCount = workloadSummary.codexRunningCount + workloadSummary.codexQueuedCount;
  const automationPendingCount = workloadSummary.automationRunningCount + workloadSummary.automationQueuedCount;

  if (key === 'test') {
    const pendingCount = codexPendingCount + automationPendingCount;

    if (pendingCount > 0) {
      return {
        pendingCount,
        message: `진행 중인 Codex Live/자동화 작업 ${pendingCount}건이 있어 즉시 재기동할 수 없습니다. 재기동 예약을 사용해 주세요.`,
      };
    }

    return null;
  }

  if (key === 'work-server') {
    return null;
  }

  return null;
}

function getRequestAccessToken(request: FastifyRequest) {
  const tokenHeader = request.headers['x-access-token'];
  return Array.isArray(tokenHeader) ? tokenHeader[0]?.trim() ?? '' : String(tokenHeader ?? '').trim();
}

function getRequestChatShareToken(request: FastifyRequest) {
  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)}`;
}

async function resolveSharedServerCommandAccessContext(request: FastifyRequest) {
  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;
  }

  const allowedAppIds = managedResource.token.allowedAppIds ?? [];
  const normalizedAllowedAppIds = new Set(allowedAppIds.map((item) => item.trim().toLowerCase()).filter(Boolean));

  if (!managedResource.token.permissions.includes('manage') || !normalizedAllowedAppIds.has('server-command')) {
    return null;
  }

  return {
    scope: 'shared' as const,
    allowedKeys: new Set<string>(['work-server', 'test']),
  };
}

function getRequestClientId(request: FastifyRequest) {
  const clientIdHeader = request.headers['x-client-id'];
  return Array.isArray(clientIdHeader) ? clientIdHeader[0]?.trim() ?? '' : String(clientIdHeader ?? '').trim();
}

function getRequestAppOrigin(request: FastifyRequest) {
  const appOriginHeader = request.headers['x-app-origin'];
  const appOrigin = Array.isArray(appOriginHeader) ? appOriginHeader[0] : appOriginHeader;

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

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

async function resolveServerCommandAccessContext(request: FastifyRequest) {
  if (getRequestAccessToken(request) === env.SERVER_COMMAND_ACCESS_TOKEN) {
    return { scope: 'full' as const };
  }

  return resolveSharedServerCommandAccessContext(request);
}

function sendAccessDenied(reply: FastifyReply) {
  reply.status(403);
  void reply.send({
    message: '권한 토큰 또는 워크서버 재기동 권한이 있는 공유채팅 링크가 필요합니다.',
  });
}

export async function registerServerCommandRoutes(app: FastifyInstance) {
  app.get('/api/server-commands', async (request, reply) => {
    const accessContext = await resolveServerCommandAccessContext(request);
    if (!accessContext) {
      sendAccessDenied(reply);
      return;
    }

    const items = await listServerCommands();
    return {
      ok: true,
      items: accessContext.scope === 'full' ? items : items.filter((item) => accessContext.allowedKeys.has(item.key)),
    };
  });

  app.post('/api/server-commands/:key/actions/restart', async (request, reply) => {
    const accessContext = await resolveServerCommandAccessContext(request);
    if (!accessContext) {
      sendAccessDenied(reply);
      return;
    }

    const { key } = serverCommandParamSchema.parse(request.params);
    if (accessContext.scope !== 'full' && !accessContext.allowedKeys.has(key)) {
      reply.status(403);
      return { ok: false, message: '현재 공유채팅 링크로는 이 서버를 재기동할 수 없습니다.' };
    }

    if (key === 'test' || key === 'work-server') {
      const workloadSummary = await getRestartReservationWorkloadSummary();
      const blockInfo = getImmediateRestartBlockInfo(key, workloadSummary);

      if (blockInfo) {
        reply.status(409);
        return {
          ok: false,
          message: blockInfo.message,
          workloadSummary,
        };
      }
    }

    try {
      const result = await restartServerCommand(key);

      return {
        ok: true,
        item: result.server,
        commandOutput: result.commandOutput,
        restartState: result.restartState,
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : '서버 재기동에 실패했습니다.';
      const statusCode = error && typeof error === 'object' && 'statusCode' in error ? Number((error as { statusCode?: unknown }).statusCode) : null;

      if (statusCode === 409) {
        reply.status(409);
        return { ok: false, message };
      }

      if (key !== 'test' && key !== 'work-server') {
        throw error;
      }

      if (!/(build|vite|tsc|typescript|npm run build|pnpm build|rollup|esbuild|compilation|compile|exit:\d+)/i.test(message)) {
        throw error;
      }

      await requestImmediateRestartRecovery(app.log, key, message);
      const server = (await listServerCommands()).find((item) => item.key === key);

      if (!server) {
        throw new Error(`${key} 서버 상태를 다시 읽지 못했습니다.`);
      }

      return {
        ok: true,
        item: server,
        commandOutput: `${message}\n\n빌드 실패를 감지해 Codex 자동 개선과 재기동 재시도를 시작했습니다.`,
        restartState: 'accepted' as const,
      };
    }
  });

  app.get('/api/server-commands/work-server/deployment', async (request, reply) => {
    const accessContext = await resolveServerCommandAccessContext(request);
    if (!accessContext) {
      sendAccessDenied(reply);
      return;
    }

    if (accessContext.scope !== 'full' && !accessContext.allowedKeys.has('work-server')) {
      reply.status(403);
      return { ok: false, message: '현재 공유채팅 링크로는 WORK 서버 배포 상태를 확인할 수 없습니다.' };
    }

    return {
      ok: true,
      item: (await readWorkServerDeploymentState()) ?? null,
    };
  });

  app.post('/api/server-commands/work-server/actions/deploy', async (request, reply) => {
    const accessContext = await resolveServerCommandAccessContext(request);
    if (!accessContext) {
      sendAccessDenied(reply);
      return;
    }

    if (accessContext.scope !== 'full' && !accessContext.allowedKeys.has('work-server')) {
      reply.status(403);
      return { ok: false, message: '현재 공유채팅 링크로는 WORK 서버를 배포할 수 없습니다.' };
    }

    const result = await deployWorkServerCommand();

    return {
      ok: true,
      item: result.server,
      commandOutput: result.commandOutput,
      restartState: result.restartState,
      deployment: result.deployment ?? result.server.deployment ?? null,
    };
  });

  app.get('/api/server-commands/test/deployment', async (request, reply) => {
    const accessContext = await resolveServerCommandAccessContext(request);
    if (!accessContext) {
      sendAccessDenied(reply);
      return;
    }

    if (accessContext.scope !== 'full' && !accessContext.allowedKeys.has('test')) {
      reply.status(403);
      return { ok: false, message: '현재 공유채팅 링크로는 TEST 서버 배포 상태를 확인할 수 없습니다.' };
    }

    return {
      ok: true,
      item: (await readTestServerDeploymentState()) ?? null,
    };
  });

  app.post('/api/server-commands/test/actions/deploy', async (request, reply) => {
    const accessContext = await resolveServerCommandAccessContext(request);
    if (!accessContext) {
      sendAccessDenied(reply);
      return;
    }

    if (accessContext.scope !== 'full' && !accessContext.allowedKeys.has('test')) {
      reply.status(403);
      return { ok: false, message: '현재 공유채팅 링크로는 TEST 서버를 배포할 수 없습니다.' };
    }

    try {
      const result = await deployTestServerCommand();

      return {
        ok: true,
        item: result.server,
        commandOutput: result.commandOutput,
        restartState: result.restartState,
        testDeployment: result.testDeployment ?? null,
      };
    } catch (error) {
      const statusCode = error && typeof error === 'object' && 'statusCode' in error ? Number((error as { statusCode?: unknown }).statusCode) : null;

      if (statusCode === 409) {
        reply.status(409);
        return { ok: false, message: error instanceof Error ? error.message : 'TEST 서버 배포가 이미 진행 중입니다.' };
      }

      throw error;
    }
  });

  app.get('/api/server-commands/restart-reservation', async (request, reply) => {
    const accessContext = await resolveServerCommandAccessContext(request);
    if (!accessContext) {
      sendAccessDenied(reply);
      return;
    }

    return {
      ok: true,
      item: await getServerRestartReservation(),
    };
  });

  app.put('/api/server-commands/restart-reservation', async (request, reply) => {
    const accessContext = await resolveServerCommandAccessContext(request);
    if (!accessContext) {
      sendAccessDenied(reply);
      return;
    }

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

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

    const parsed = restartReservationBodySchema.parse(payload ?? {});

    if (accessContext.scope !== 'full' && parsed.target !== 'work-server') {
      return reply.status(403).send({ message: '현재 공유채팅 링크로는 WORK 서버 재기동 예약만 사용할 수 있습니다.' });
    }

    return {
      ok: true,
      item: await scheduleServerRestartReservation({
        target: parsed.target,
        clientId: getRequestClientId(request),
        appOrigin: getRequestAppOrigin(request),
        autoExecuteDelaySeconds: parsed.autoExecuteDelaySeconds,
      }),
    };
  });

  app.post('/api/server-commands/restart-reservation/confirm', async (request, reply) => {
    const accessContext = await resolveServerCommandAccessContext(request);
    if (!accessContext) {
      sendAccessDenied(reply);
      return;
    }

    return {
      ok: true,
      item: await confirmServerRestartReservation(app.log),
    };
  });

  app.delete('/api/server-commands/restart-reservation', async (request, reply) => {
    const accessContext = await resolveServerCommandAccessContext(request);
    if (!accessContext) {
      sendAccessDenied(reply);
      return;
    }

    return {
      ok: true,
      item: await cancelServerRestartReservation(),
    };
  });
}
