import { Injectable, NotFoundException } from "@nestjs/common";
import { PrismaService } from "../../prisma/prisma.service";
import type { EntryGraphResponse, GraphNode, GraphEdge } from "../entities/graph.entity";
import { KnowledgeCacheService } from "./cache.service";

/**
 * Service for knowledge graph operations
 */
@Injectable()
export class GraphService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly cache: KnowledgeCacheService
  ) {}

  /**
   * Get entry-centered graph view
   * Returns the entry and all connected nodes up to specified depth
   */
  async getEntryGraph(
    workspaceId: string,
    entryId: string,
    maxDepth = 1
  ): Promise<EntryGraphResponse> {
    // Check cache first
    const cached = await this.cache.getGraph<EntryGraphResponse>(workspaceId, entryId, maxDepth);
    if (cached) {
      return cached;
    }

    // Verify entry exists
    const centerEntry = await this.prisma.knowledgeEntry.findUnique({
      where: { id: entryId },
      include: {
        tags: {
          include: {
            tag: true,
          },
        },
      },
    });

    if (!centerEntry || centerEntry.workspaceId !== workspaceId) {
      throw new NotFoundException("Entry not found");
    }

    // Build graph using BFS
    const visitedNodes = new Set<string>();
    const nodes: GraphNode[] = [];
    const edges: GraphEdge[] = [];
    const nodeDepths = new Map<string, number>();

    // Queue: [entryId, depth]
    const queue: [string, number][] = [[entryId, 0]];
    visitedNodes.add(entryId);
    nodeDepths.set(entryId, 0);

    while (queue.length > 0) {
      const item = queue.shift();
      if (!item) break; // Should never happen, but satisfy TypeScript
      const [currentId, depth] = item;

      // Fetch current entry with related data
      const currentEntry = await this.prisma.knowledgeEntry.findUnique({
        where: { id: currentId },
        include: {
          tags: {
            include: {
              tag: true,
            },
          },
          outgoingLinks: {
            include: {
              target: {
                select: {
                  id: true,
                  slug: true,
                  title: true,
                  summary: true,
                },
              },
            },
          },
          incomingLinks: {
            include: {
              source: {
                select: {
                  id: true,
                  slug: true,
                  title: true,
                  summary: true,
                },
              },
            },
          },
        },
      });

      if (!currentEntry) continue;

      // Add current node
      const graphNode: GraphNode = {
        id: currentEntry.id,
        slug: currentEntry.slug,
        title: currentEntry.title,
        summary: currentEntry.summary,
        tags: currentEntry.tags.map((et) => ({
          id: et.tag.id,
          name: et.tag.name,
          slug: et.tag.slug,
          color: et.tag.color,
        })),
        depth,
      };
      nodes.push(graphNode);

      // Continue BFS if not at max depth
      if (depth < maxDepth) {
        // Process outgoing links (only resolved ones)
        for (const link of currentEntry.outgoingLinks) {
          // Skip unresolved links
          if (!link.targetId || !link.resolved) continue;

          // Add edge
          edges.push({
            id: link.id,
            sourceId: link.sourceId,
            targetId: link.targetId,
            linkText: link.linkText,
          });

          // Add target to queue if not visited
          if (!visitedNodes.has(link.targetId)) {
            visitedNodes.add(link.targetId);
            nodeDepths.set(link.targetId, depth + 1);
            queue.push([link.targetId, depth + 1]);
          }
        }

        // Process incoming links (only resolved ones)
        for (const link of currentEntry.incomingLinks) {
          // Skip unresolved links
          if (!link.targetId || !link.resolved) continue;

          // Add edge
          const edgeExists = edges.some(
            (e) => e.sourceId === link.sourceId && e.targetId === link.targetId
          );
          if (!edgeExists) {
            edges.push({
              id: link.id,
              sourceId: link.sourceId,
              targetId: link.targetId,
              linkText: link.linkText,
            });
          }

          // Add source to queue if not visited
          if (!visitedNodes.has(link.sourceId)) {
            visitedNodes.add(link.sourceId);
            nodeDepths.set(link.sourceId, depth + 1);
            queue.push([link.sourceId, depth + 1]);
          }
        }
      }
    }

    // Find center node
    const centerNode = nodes.find((n) => n.id === entryId);
    if (!centerNode) {
      throw new Error(`Center node ${entryId} not found in graph`);
    }

    const result: EntryGraphResponse = {
      centerNode,
      nodes,
      edges,
      stats: {
        totalNodes: nodes.length,
        totalEdges: edges.length,
        maxDepth,
      },
    };

    // Cache the result
    await this.cache.setGraph(workspaceId, entryId, maxDepth, result);

    return result;
  }
}
