/**
 * llm-svc — LLM provider management microservice
 * API: GET/POST/PATCH/DELETE /api/v1/providers, GET/POST /api/v1/providers/:id/models
 * Config: data/llm-providers.json, envOnlyMode via LLM_PROVIDER=ollama|timeweb
 */

import Fastify from 'fastify';
import cors from '@fastify/cors';
import { z } from 'zod';
import ModelRegistry from './lib/models/registry.js';
import { isEnvOnlyMode } from './lib/config/serverRegistry.js';
import path from 'node:path';
import fs from 'node:fs';

const DATA_DIR = process.env.DATA_DIR
  ? path.resolve(process.cwd(), process.env.DATA_DIR)
  : process.cwd();
const dataDir = path.join(DATA_DIR, 'data');
if (!fs.existsSync(dataDir)) {
  fs.mkdirSync(dataDir, { recursive: true });
}

const PORT = parseInt(process.env.PORT ?? '3020', 10);

const providerPostSchema = z.object({
  type: z.string().min(1),
  name: z.string().min(1),
  config: z.record(z.string(), z.unknown()),
});

const providerPatchSchema = z.object({
  name: z.string().min(1),
  config: z.record(z.string(), z.unknown()),
});

const modelPostSchema = z.object({
  type: z.enum(['chat', 'embedding']),
  key: z.string().min(1),
  name: z.string().min(1),
});

const app = Fastify({ logger: true });
const corsOrigin = process.env.ALLOWED_ORIGINS
  ? process.env.ALLOWED_ORIGINS.split(',')
      .map((s) => s.trim())
      .filter(Boolean)
  : true;
await app.register(cors, { origin: corsOrigin });

app.get('/health', async () => ({ status: 'ok' }));
app.get('/ready', async () => ({ status: 'ready' }));

app.get('/metrics', async (_req, reply) => {
  reply.header('Content-Type', 'text/plain; charset=utf-8');
  return reply.send(
    '# HELP gooseek_up Service is up (1) or down (0)\n' +
      '# TYPE gooseek_up gauge\n' +
      'gooseek_up 1\n',
  );
});

/* --- Providers --- */
app.get<{ Querystring: { internal?: string } }>('/api/v1/providers', async (req, reply) => {
  try {
    const registry = new ModelRegistry();
    const providers = await registry.getActiveProviders();
    const internal = req.query.internal === '1';
    if (internal) {
      const { getConfiguredModelProviders } = await import('./lib/config/serverRegistry.js');
      const fullProviders = getConfiguredModelProviders();
      const merged = providers.map((p) => {
        const full = fullProviders.find((f) => f.id === p.id);
        return full
          ? { ...full, chatModels: p.chatModels, embeddingModels: p.embeddingModels }
          : p;
      });
      return reply.send({ providers: merged, envOnlyMode: isEnvOnlyMode() });
    }
    return reply.send({
      providers,
      envOnlyMode: isEnvOnlyMode(),
    });
  } catch (err) {
    app.log.error(err);
    return reply.status(500).send({ message: 'An error has occurred.' });
  }
});

app.post<{ Body: unknown }>('/api/v1/providers', async (req, reply) => {
  if (isEnvOnlyMode()) {
    return reply.status(405).send({ message: 'Not available in env-only mode.' });
  }
  const parsed = providerPostSchema.safeParse(req.body);
  if (!parsed.success) {
    return reply.status(400).send({
      message: 'Missing required fields (type, name, config).',
      error: parsed.error.issues,
    });
  }
  try {
    const registry = new ModelRegistry();
    const provider = await registry.addProvider(
      parsed.data.type,
      parsed.data.name,
      parsed.data.config,
    );
    return reply.send({ provider });
  } catch (err) {
    app.log.error(err);
    return reply.status(500).send({ message: 'An error has occurred.' });
  }
});

app.patch<{ Params: { id: string }; Body: unknown }>(
  '/api/v1/providers/:id',
  async (req, reply) => {
    if (isEnvOnlyMode()) {
      return reply
        .status(405)
        .send({ message: 'Not available in env-only mode.' });
    }
    const { id } = req.params;
    const parsed = providerPatchSchema.safeParse(req.body);
    if (!parsed.success) {
      return reply.status(400).send({
        message: 'Missing required fields (name, config).',
        error: parsed.error.issues,
      });
    }
    if (!id) {
      return reply.status(400).send({ message: 'Provider ID is required.' });
    }
    try {
      const registry = new ModelRegistry();
      const provider = await registry.updateProvider(
        id,
        parsed.data.name,
        parsed.data.config,
      );
      return reply.send({ provider });
    } catch (err) {
      app.log.error(err);
      return reply.status(500).send({ message: 'An error has occurred.' });
    }
  },
);

app.delete<{ Params: { id: string } }>(
  '/api/v1/providers/:id',
  async (req, reply) => {
    if (isEnvOnlyMode()) {
      return reply
        .status(405)
        .send({ message: 'Not available in env-only mode.' });
    }
    const { id } = req.params;
    if (!id) {
      return reply.status(400).send({ message: 'Provider ID is required.' });
    }
    try {
      const registry = new ModelRegistry();
      await registry.removeProvider(id);
      return reply.send({ message: 'Provider deleted successfully.' });
    } catch (err) {
      app.log.error(err);
      return reply.status(500).send({ message: 'An error has occurred.' });
    }
  },
);

app.get<{ Params: { id: string } }>(
  '/api/v1/providers/:id',
  async (req, reply) => {
    const { id } = req.params;
    if (!id) {
      return reply.status(400).send({ message: 'Provider ID is required.' });
    }
    try {
      const registry = new ModelRegistry();
      const providers = await registry.getActiveProviders();
      const provider = providers.find((p) => p.id === id);
      if (!provider) {
        return reply.status(404).send({ message: 'Provider not found.' });
      }
      const { getConfiguredModelProviderById } = await import('./lib/config/serverRegistry.js');
      const fullProvider = getConfiguredModelProviderById(id);
      if (!fullProvider) {
        return reply.send(provider);
      }
      return reply.send({
        ...fullProvider,
        chatModels: provider.chatModels,
        embeddingModels: provider.embeddingModels,
      });
    } catch (err) {
      app.log.error(err);
      return reply.status(500).send({ message: 'An error has occurred.' });
    }
  },
);

app.get<{ Params: { id: string } }>(
  '/api/v1/providers/:id/models',
  async (req, reply) => {
    const { id } = req.params;
    if (!id) {
      return reply.status(400).send({ message: 'Provider ID is required.' });
    }
    try {
      const registry = new ModelRegistry();
      const providers = await registry.getActiveProviders();
      const provider = providers.find((p) => p.id === id);
      if (!provider) {
        return reply.status(404).send({ message: 'Provider not found.' });
      }
      return reply.send({
        chatModels: provider.chatModels,
        embeddingModels: provider.embeddingModels,
      });
    } catch (err) {
      app.log.error(err);
      return reply.status(500).send({ message: 'An error has occurred.' });
    }
  },
);

app.post<{ Params: { id: string }; Body: unknown }>(
  '/api/v1/providers/:id/models',
  async (req, reply) => {
    if (isEnvOnlyMode()) {
      return reply
        .status(405)
        .send({ message: 'Not available in env-only mode.' });
    }
    const { id } = req.params;
    const parsed = modelPostSchema.safeParse(req.body);
    if (!parsed.success) {
      return reply.status(400).send({
        message: 'key, name and type (chat|embedding) must be provided',
        error: parsed.error.issues,
      });
    }
    if (!id) {
      return reply.status(400).send({ message: 'Provider ID is required.' });
    }
    try {
      const registry = new ModelRegistry();
      await registry.addProviderModel(id, parsed.data.type, {
        key: parsed.data.key,
        name: parsed.data.name,
      });
      return reply.send({ message: 'Model added successfully.' });
    } catch (err) {
      app.log.error(err);
      return reply.status(500).send({ message: 'An error has occurred.' });
    }
  },
);

const modelDeleteSchema = z.object({
  type: z.enum(['chat', 'embedding']),
  key: z.string().min(1),
});
app.delete<{ Params: { id: string }; Body: unknown }>(
  '/api/v1/providers/:id/models',
  async (req, reply) => {
    if (isEnvOnlyMode()) {
      return reply
        .status(405)
        .send({ message: 'Not available in env-only mode.' });
    }
    const { id } = req.params;
    const parsed = modelDeleteSchema.safeParse(req.body ?? {});
    if (!parsed.success) {
      return reply.status(400).send({
        message: 'type and key must be provided',
        error: parsed.error.issues,
      });
    }
    if (!id) {
      return reply.status(400).send({ message: 'Provider ID is required.' });
    }
    try {
      const registry = new ModelRegistry();
      await registry.removeProviderModel(id, parsed.data.type, parsed.data.key);
      return reply.send({ message: 'Model deleted successfully.' });
    } catch (err) {
      app.log.error(err);
      return reply.status(500).send({ message: 'An error has occurred.' });
    }
  },
);

try {
  await app.listen({ port: PORT, host: '0.0.0.0' });
  console.log('llm-svc listening on :' + PORT);
} catch (err) {
  console.error(err);
  process.exit(1);
}
