import {
  Injectable,
  NotFoundException,
  ConflictException,
  BadRequestException,
} from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
import type { CreateTagDto, UpdateTagDto } from "./dto";

/**
 * Service for managing knowledge tags
 */
@Injectable()
export class TagsService {
  constructor(private readonly prisma: PrismaService) {}

  /**
   * Generate a URL-friendly slug from a tag name
   */
  private generateSlug(name: string): string {
    return name
      .toLowerCase()
      .trim()
      .replace(/[^a-z0-9]+/g, "-") // Replace non-alphanumeric with hyphens
      .replace(/^-+|-+$/g, ""); // Remove leading/trailing hyphens
  }

  /**
   * Create a new tag
   */
  async create(
    workspaceId: string,
    createTagDto: CreateTagDto
  ): Promise<{
    id: string;
    workspaceId: string;
    name: string;
    slug: string;
    color: string | null;
    description: string | null;
  }> {
    // Generate slug if not provided
    const slug = createTagDto.slug ?? this.generateSlug(createTagDto.name);

    // Validate slug format if provided
    if (createTagDto.slug) {
      // eslint-disable-next-line security/detect-unsafe-regex
      const slugPattern = /^[a-z0-9]+(-[a-z0-9]+)*$/;
      if (!slugPattern.test(slug)) {
        throw new BadRequestException(
          "Invalid slug format. Must be lowercase, alphanumeric, and may contain hyphens."
        );
      }
    }

    // Check if slug already exists in workspace
    const existingTag = await this.prisma.knowledgeTag.findUnique({
      where: {
        workspaceId_slug: {
          workspaceId,
          slug,
        },
      },
    });

    if (existingTag) {
      throw new ConflictException(`Tag with slug '${slug}' already exists in this workspace`);
    }

    // Create tag
    const tag = await this.prisma.knowledgeTag.create({
      data: {
        workspaceId,
        name: createTagDto.name,
        slug,
        color: createTagDto.color ?? null,
        description: createTagDto.description ?? null,
      },
      select: {
        id: true,
        workspaceId: true,
        name: true,
        slug: true,
        color: true,
        description: true,
      },
    });

    return tag;
  }

  /**
   * Get all tags for a workspace
   */
  async findAll(workspaceId: string): Promise<
    {
      id: string;
      workspaceId: string;
      name: string;
      slug: string;
      color: string | null;
      description: string | null;
      _count: {
        entries: number;
      };
    }[]
  > {
    const tags = await this.prisma.knowledgeTag.findMany({
      where: {
        workspaceId,
      },
      include: {
        _count: {
          select: {
            entries: true,
          },
        },
      },
      orderBy: {
        name: "asc",
      },
    });

    return tags;
  }

  /**
   * Get a single tag by slug
   */
  async findOne(
    slug: string,
    workspaceId: string
  ): Promise<{
    id: string;
    workspaceId: string;
    name: string;
    slug: string;
    color: string | null;
    description: string | null;
    _count: {
      entries: number;
    };
  }> {
    const tag = await this.prisma.knowledgeTag.findUnique({
      where: {
        workspaceId_slug: {
          workspaceId,
          slug,
        },
      },
      include: {
        _count: {
          select: {
            entries: true,
          },
        },
      },
    });

    if (!tag) {
      throw new NotFoundException(`Tag with slug '${slug}' not found in this workspace`);
    }

    return tag;
  }

  /**
   * Update a tag
   */
  async update(
    slug: string,
    workspaceId: string,
    updateTagDto: UpdateTagDto
  ): Promise<{
    id: string;
    workspaceId: string;
    name: string;
    slug: string;
    color: string | null;
    description: string | null;
  }> {
    // Verify tag exists
    await this.findOne(slug, workspaceId);

    // If name is being updated, regenerate slug
    let newSlug = slug;
    if (updateTagDto.name) {
      newSlug = this.generateSlug(updateTagDto.name);

      // If slug changed, check for conflicts
      if (newSlug !== slug) {
        const existingTag = await this.prisma.knowledgeTag.findUnique({
          where: {
            workspaceId_slug: {
              workspaceId,
              slug: newSlug,
            },
          },
        });

        if (existingTag) {
          throw new ConflictException(
            `Tag with slug '${newSlug}' already exists in this workspace`
          );
        }
      }
    }

    // Update tag - only include defined fields
    const updateData: {
      name?: string;
      slug?: string;
      color?: string | null;
      description?: string | null;
    } = {};

    if (updateTagDto.name !== undefined) updateData.name = updateTagDto.name;
    if (newSlug !== slug) updateData.slug = newSlug; // Only update slug if it changed
    if (updateTagDto.color !== undefined) updateData.color = updateTagDto.color;
    if (updateTagDto.description !== undefined) updateData.description = updateTagDto.description;

    const tag = await this.prisma.knowledgeTag.update({
      where: {
        workspaceId_slug: {
          workspaceId,
          slug,
        },
      },
      data: updateData,
      select: {
        id: true,
        workspaceId: true,
        name: true,
        slug: true,
        color: true,
        description: true,
      },
    });

    return tag;
  }

  /**
   * Delete a tag
   */
  async remove(slug: string, workspaceId: string): Promise<void> {
    // Verify tag exists
    await this.findOne(slug, workspaceId);

    // Delete tag (cascade will remove entry associations)
    await this.prisma.knowledgeTag.delete({
      where: {
        workspaceId_slug: {
          workspaceId,
          slug,
        },
      },
    });
  }

  /**
   * Get all entries with a specific tag
   */
  async getEntriesWithTag(
    slug: string,
    workspaceId: string
  ): Promise<
    {
      id: string;
      slug: string;
      title: string;
      summary: string | null;
      status: string;
      visibility: string;
      createdAt: Date;
      updatedAt: Date;
    }[]
  > {
    // Verify tag exists
    const tag = await this.findOne(slug, workspaceId);

    // Get entries with this tag
    const entries = await this.prisma.knowledgeEntry.findMany({
      where: {
        workspaceId,
        tags: {
          some: {
            tagId: tag.id,
          },
        },
      },
      select: {
        id: true,
        slug: true,
        title: true,
        summary: true,
        status: true,
        visibility: true,
        createdAt: true,
        updatedAt: true,
      },
      orderBy: {
        updatedAt: "desc",
      },
    });

    return entries;
  }

  /**
   * Find or create tags by slugs (for entry creation/update)
   * Returns existing tags and creates new ones if autoCreate is true
   */
  async findOrCreateTags(
    workspaceId: string,
    tagSlugs: string[],
    autoCreate = false
  ): Promise<{ id: string; slug: string; name: string }[]> {
    const uniqueSlugs = [...new Set(tagSlugs)];
    const tags: { id: string; slug: string; name: string }[] = [];

    for (const slug of uniqueSlugs) {
      try {
        const tag = await this.prisma.knowledgeTag.findUnique({
          where: {
            workspaceId_slug: {
              workspaceId,
              slug,
            },
          },
          select: {
            id: true,
            slug: true,
            name: true,
          },
        });

        if (tag) {
          tags.push(tag);
        } else if (autoCreate) {
          // Auto-create tag from slug
          const name = slug
            .split("-")
            .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
            .join(" ");

          const newTag = await this.create(workspaceId, {
            name,
            slug,
          });

          tags.push({
            id: newTag.id,
            slug: newTag.slug,
            name: newTag.name,
          });
        } else {
          throw new NotFoundException(`Tag with slug '${slug}' not found in this workspace`);
        }
      } catch (error) {
        // If it's a conflict error during auto-create, try to fetch again
        if (autoCreate && error instanceof ConflictException) {
          const tag = await this.prisma.knowledgeTag.findUnique({
            where: {
              workspaceId_slug: {
                workspaceId,
                slug,
              },
            },
            select: {
              id: true,
              slug: true,
              name: true,
            },
          });

          if (tag) {
            tags.push(tag);
            continue;
          }
        }

        throw error;
      }
    }

    return tags;
  }
}
