import { randomUUID } from 'node:crypto';
import type { FastifyInstance } from 'fastify';
import { BadRequestError, ForbiddenError } from '../../lib/errors.js';
import {
  CreateKnowledgeBaseSchema,
  CreateKnowledgeSourceSchema,
  KnowledgeBaseDoc,
  KnowledgeChunkDoc,
  KnowledgeSourceDoc,
  ListKnowledgeBasesQuerySchema,
  RetrievalQuerySchema,
  UpdateKnowledgeBaseSchema,
  UpsertKnowledgeChunksSchema,
} from './types.js';
import * as repo from './repository.js';

function requireAdmin(req: { jwtPayload?: { sub?: string; role?: string; productId?: string } }): {
  userId: string;
  productId: string;
} {
  const payload = req.jwtPayload;
  if (!payload?.sub) throw new ForbiddenError('Authentication required');
  if (!payload.role || !['super_admin', 'admin'].includes(payload.role)) {
    throw new ForbiddenError('Admin access required');
  }
  return {
    userId: payload.sub,
    productId: payload.productId ?? process.env.DEFAULT_PRODUCT_ID ?? 'lysnrai',
  };
}

function validationError(message: string): never {
  throw new BadRequestError(message);
}

function scoreChunk(query: string, chunk: KnowledgeChunkDoc): number {
  const q = query.toLowerCase();
  const text = chunk.contentText.toLowerCase();
  if (text.includes(q)) return 3;
  const words = q.split(/\s+/).filter(Boolean);
  let score = 0;
  for (const word of words) {
    if (text.includes(word)) score += 1;
    if (chunk.tags.some(tag => tag.toLowerCase() === word)) score += 2;
  }
  return score;
}

export async function knowledgeRoutes(app: FastifyInstance) {
  app.get('/knowledge/bases', async req => {
    const access = requireAdmin(req);
    const parsed = ListKnowledgeBasesQuerySchema.safeParse(req.query);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    return repo.listBases(access.productId, parsed.data);
  });

  app.post('/knowledge/bases', async req => {
    const access = requireAdmin(req);
    const parsed = CreateKnowledgeBaseSchema.safeParse(req.body);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    const now = new Date().toISOString();
    const doc: KnowledgeBaseDoc = {
      id: `kb_${randomUUID()}`,
      productId: access.productId,
      orgId: parsed.data.orgId,
      workspaceId: parsed.data.workspaceId,
      name: parsed.data.name,
      description: parsed.data.description,
      status: 'draft',
      embeddingModel: parsed.data.embeddingModel,
      retrievalStrategy: parsed.data.retrievalStrategy,
      tags: parsed.data.tags,
      metadata: parsed.data.metadata,
      createdBy: access.userId,
      createdAt: now,
      updatedAt: now,
    };
    return repo.createBase(doc);
  });

  app.get('/knowledge/bases/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    return repo.getBase(id, access.productId);
  });

  app.patch('/knowledge/bases/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const parsed = UpdateKnowledgeBaseSchema.safeParse(req.body);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    return repo.updateBase(id, access.productId, parsed.data);
  });

  app.get('/knowledge/bases/:id/sources', async req => {
    requireAdmin(req);
    const { id } = req.params as { id: string };
    return repo.listSources(id);
  });

  app.post('/knowledge/bases/:id/sources', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    await repo.getBase(id, access.productId);
    const parsed = CreateKnowledgeSourceSchema.safeParse(req.body);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    const now = new Date().toISOString();
    const doc: KnowledgeSourceDoc = {
      id: `ksrc_${randomUUID()}`,
      knowledgeBaseId: id,
      productId: access.productId,
      type: parsed.data.type,
      status: 'pending',
      title: parsed.data.title,
      uri: parsed.data.uri,
      extractionJobId: parsed.data.extractionJobId,
      contentHash: parsed.data.contentHash,
      tags: parsed.data.tags,
      metadata: parsed.data.metadata,
      createdAt: now,
      updatedAt: now,
    };
    return repo.createSource(doc);
  });

  app.get('/knowledge/bases/:id/chunks', async req => {
    requireAdmin(req);
    const { id } = req.params as { id: string };
    const { sourceId, limit } = req.query as { sourceId?: string; limit?: string };
    return repo.listChunks(id, {
      sourceId,
      limit: limit ? Number(limit) : undefined,
    });
  });

  app.post('/knowledge/sources/:id/chunks', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const { knowledgeBaseId } = req.query as { knowledgeBaseId: string };
    const source = await repo.getSource(id, knowledgeBaseId);
    if (source.productId !== access.productId) {
      throw new ForbiddenError('Knowledge source is not in the current product');
    }
    const parsed = UpsertKnowledgeChunksSchema.safeParse(req.body);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }

    const now = new Date().toISOString();
    const chunks: KnowledgeChunkDoc[] = parsed.data.chunks.map(chunk => ({
      id: `${knowledgeBaseId}:${id}:${chunk.ordinal}`,
      knowledgeBaseId,
      sourceId: id,
      productId: access.productId,
      ordinal: chunk.ordinal,
      contentText: chunk.contentText,
      tokenCount: chunk.tokenCount,
      embeddingStatus: chunk.embeddingStatus,
      citations: chunk.citations,
      tags: chunk.tags,
      metadata: chunk.metadata,
      createdAt: now,
    }));

    const saved = await repo.upsertChunks(chunks);
    if (parsed.data.sourceStatus) {
      await repo.updateSource(id, knowledgeBaseId, { status: parsed.data.sourceStatus });
    }
    return saved;
  });

  app.post('/knowledge/bases/:id/retrieval-query', async req => {
    requireAdmin(req);
    const { id } = req.params as { id: string };
    const parsed = RetrievalQuerySchema.safeParse(req.body);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    const chunks = await repo.listChunks(id, {
      sourceId: parsed.data.sourceId,
      limit: 1000,
    });
    const matches = chunks
      .map(chunk => ({
        chunk,
        score: scoreChunk(parsed.data.query, chunk),
      }))
      .filter(item => item.score > 0)
      .sort((a, b) => b.score - a.score || a.chunk.ordinal - b.chunk.ordinal)
      .slice(0, parsed.data.limit);

    return {
      query: parsed.data.query,
      matches: matches.map(item => ({
        chunkId: item.chunk.id,
        sourceId: item.chunk.sourceId,
        score: item.score,
        preview: item.chunk.contentText.slice(0, 240),
        citations: item.chunk.citations,
        tags: item.chunk.tags,
      })),
    };
  });

  // ── Knowledge base stats ───────────────────────────────
  app.get('/knowledge/bases/:id/stats', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    await repo.getBase(id, access.productId);
    return repo.getBaseStats(id);
  });

  // ── Delete knowledge base (draft only) ─────────────────
  app.delete('/knowledge/bases/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const base = await repo.getBase(id, access.productId);
    if (base.status !== 'draft') {
      throw new BadRequestError('Only draft knowledge bases can be deleted');
    }
    await repo.deleteBase(id, access.productId);
    return { deleted: true };
  });

  // ── Delete knowledge source ────────────────────────────
  app.delete('/knowledge/bases/:id/sources/:sourceId', async req => {
    const access = requireAdmin(req);
    const { id, sourceId } = req.params as { id: string; sourceId: string };
    await repo.getBase(id, access.productId);
    await repo.getSource(sourceId, id);
    await repo.deleteSource(sourceId, id);
    return { deleted: true };
  });

  // ── Search chunks (text search) ────────────────────────
  app.post('/knowledge/bases/:id/search', async req => {
    requireAdmin(req);
    const { id } = req.params as { id: string };
    const parsed = RetrievalQuerySchema.safeParse(req.body);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    const results = await repo.searchChunks(id, parsed.data.query, {
      sourceId: parsed.data.sourceId,
      limit: parsed.data.limit,
    });
    return {
      query: parsed.data.query,
      count: results.length,
      chunks: results.map(c => ({
        id: c.id,
        sourceId: c.sourceId,
        ordinal: c.ordinal,
        preview: c.contentText.slice(0, 240),
        tokenCount: c.tokenCount,
        citations: c.citations,
        tags: c.tags,
      })),
    };
  });
}
