import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import { z } from 'zod';
import { env } from '../config/env.js';
import {
  copyResourceManagerItem,
  createResourceManagerDirectory,
  createResourceManagerFile,
  deleteResourceManagerItem,
  ensureResourceManagerRoot,
  getResourceManagerTree,
  listResourceManagerDirectory,
  moveResourceManagerItem,
  openResourceManagerPreviewStream,
  readResourceManagerFile,
  saveResourceManagerFile,
  uploadResourceManagerFile,
} from '../services/resource-manager-service.js';

const queryPathSchema = z.object({
  path: z.string().trim().optional().default(''),
});

const createDirectoryBodySchema = z.object({
  parentPath: z.string().trim().optional().default(''),
  name: z.string().trim().min(1).max(255),
});

const createFileBodySchema = z.object({
  parentPath: z.string().trim().optional().default(''),
  name: z.string().trim().min(1).max(255),
  content: z.string().optional().default(''),
});

const saveFileBodySchema = z.object({
  path: z.string().trim().min(1),
  content: z.string(),
});

const uploadFileBodySchema = z.object({
  parentPath: z.string().trim().optional().default(''),
  fileName: z.string().trim().min(1).max(255),
  contentBase64: z.string().trim().min(1),
});

const copyMoveBodySchema = z.object({
  path: z.string().trim().min(1),
  targetDirectoryPath: z.string().trim().optional().default(''),
  nextName: z.string().trim().max(255).optional().nullable(),
});

export function resolveSingleRange(rangeHeader: string | undefined, fileSize: number) {
  const rangeValue = String(rangeHeader ?? '').trim();

  if (!rangeValue) {
    return null;
  }

  const match = /^bytes=(\d*)-(\d*)$/u.exec(rangeValue);

  if (!match) {
    return { isValid: false } as const;
  }

  const [, startRaw, endRaw] = match;

  if (!startRaw && !endRaw) {
    return { isValid: false } as const;
  }

  if (!startRaw) {
    const suffixLength = Number(endRaw);

    if (!Number.isInteger(suffixLength) || suffixLength <= 0) {
      return { isValid: false } as const;
    }

    const start = Math.max(fileSize - suffixLength, 0);
    const end = fileSize - 1;
    return start <= end ? { isValid: true, start, end } as const : { isValid: false } as const;
  }

  const start = Number(startRaw);
  const end = endRaw ? Number(endRaw) : fileSize - 1;

  if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start || start >= fileSize) {
    return { isValid: false } as const;
  }

  return {
    isValid: true,
    start,
    end: Math.min(end, fileSize - 1),
  } as const;
}

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

function ensureAuthorized(request: FastifyRequest, reply: FastifyReply, tokenOverride?: string | null) {
  if ((tokenOverride ?? getRequestAccessToken(request)) === env.SERVER_COMMAND_ACCESS_TOKEN) {
    return true;
  }

  reply.status(403);
  void reply.send({
    message: '권한 토큰이 필요합니다.',
  });
  return false;
}

function resolveRepoRootPath() {
  return env.SERVER_COMMAND_MAIN_PROJECT_ROOT || env.PLAN_MAIN_PROJECT_REPO_PATH || env.PLAN_GIT_REPO_PATH;
}

export async function registerResourceManagerRoutes(app: FastifyInstance) {
  app.get('/api/resource-manager/tree', async (request, reply) => {
    if (!ensureAuthorized(request, reply)) {
      return;
    }

    const repoRootPath = resolveRepoRootPath();
    await ensureResourceManagerRoot(repoRootPath);

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

  app.get('/api/resource-manager/directory', async (request, reply) => {
    if (!ensureAuthorized(request, reply)) {
      return;
    }

    const query = queryPathSchema.parse(request.query ?? {});

    return {
      ok: true,
      item: await listResourceManagerDirectory(resolveRepoRootPath(), query.path),
    };
  });

  app.get('/api/resource-manager/file', async (request, reply) => {
    if (!ensureAuthorized(request, reply)) {
      return;
    }

    const query = queryPathSchema.extend({
      path: z.string().trim().min(1),
    }).parse(request.query ?? {});

    return {
      ok: true,
      item: await readResourceManagerFile(resolveRepoRootPath(), query.path),
    };
  });

  app.get('/api/resource-manager/preview/*', async (request, reply) => {
    const query = z.object({
      token: z.string().trim().optional(),
    }).parse(request.query ?? {});

    if (!ensureAuthorized(request, reply, query.token ?? null)) {
      return;
    }

    const wildcard = String((request.params as { '*': string | undefined })['*'] ?? '').trim();
    const preview = await openResourceManagerPreviewStream(resolveRepoRootPath(), decodeURIComponent(wildcard));
    const rangeHeader = Array.isArray(request.headers.range) ? request.headers.range[0] : request.headers.range;
    const range = resolveSingleRange(rangeHeader, preview.size);

    reply.header('Cache-Control', 'no-store');
    reply.header('Accept-Ranges', 'bytes');
    reply.type(preview.contentType);

    if (range) {
      if (!range.isValid) {
        reply.status(416);
        reply.header('Content-Range', `bytes */${preview.size}`);
        return reply.send();
      }

      const contentLength = range.end - range.start + 1;
      reply.status(206);
      reply.header('Content-Range', `bytes ${range.start}-${range.end}/${preview.size}`);
      reply.header('Content-Length', String(contentLength));
      return reply.send(preview.createStream({ start: range.start, end: range.end }));
    }

    reply.header('Content-Length', String(preview.size));
    return reply.send(preview.createStream());
  });

  app.post('/api/resource-manager/directories', async (request, reply) => {
    if (!ensureAuthorized(request, reply)) {
      return;
    }

    const payload = createDirectoryBodySchema.parse(request.body ?? {});
    await createResourceManagerDirectory(resolveRepoRootPath(), payload.parentPath, payload.name);

    return {
      ok: true,
    };
  });

  app.post('/api/resource-manager/files', async (request, reply) => {
    if (!ensureAuthorized(request, reply)) {
      return;
    }

    const payload = createFileBodySchema.parse(request.body ?? {});
    await createResourceManagerFile(resolveRepoRootPath(), payload.parentPath, payload.name, payload.content);

    return {
      ok: true,
    };
  });

  app.put('/api/resource-manager/files/content', async (request, reply) => {
    if (!ensureAuthorized(request, reply)) {
      return;
    }

    const payload = saveFileBodySchema.parse(request.body ?? {});
    await saveResourceManagerFile(resolveRepoRootPath(), payload.path, payload.content);

    return {
      ok: true,
    };
  });

  app.post('/api/resource-manager/files/upload', async (request, reply) => {
    if (!ensureAuthorized(request, reply)) {
      return;
    }

    const payload = uploadFileBodySchema.parse(request.body ?? {});

    return {
      ok: true,
      item: await uploadResourceManagerFile(
        resolveRepoRootPath(),
        payload.parentPath,
        payload.fileName,
        payload.contentBase64,
      ),
    };
  });

  app.post('/api/resource-manager/items/copy', async (request, reply) => {
    if (!ensureAuthorized(request, reply)) {
      return;
    }

    const payload = copyMoveBodySchema.parse(request.body ?? {});

    return {
      ok: true,
      item: await copyResourceManagerItem(
        resolveRepoRootPath(),
        payload.path,
        payload.targetDirectoryPath,
        payload.nextName,
      ),
    };
  });

  app.post('/api/resource-manager/items/move', async (request, reply) => {
    if (!ensureAuthorized(request, reply)) {
      return;
    }

    const payload = copyMoveBodySchema.parse(request.body ?? {});

    return {
      ok: true,
      item: await moveResourceManagerItem(
        resolveRepoRootPath(),
        payload.path,
        payload.targetDirectoryPath,
        payload.nextName,
      ),
    };
  });

  app.delete('/api/resource-manager/items', async (request, reply) => {
    if (!ensureAuthorized(request, reply)) {
      return;
    }

    const query = queryPathSchema.extend({
      path: z.string().trim().min(1),
    }).parse(request.query ?? {});
    await deleteResourceManagerItem(resolveRepoRootPath(), query.path);

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