import { Injectable, NotFoundException } from "@nestjs/common";
import { Prisma, Domain } from "@prisma/client";
import { PrismaService } from "../prisma/prisma.service";
import { ActivityService } from "../activity/activity.service";
import type { CreateDomainDto, UpdateDomainDto, QueryDomainsDto } from "./dto";

type DomainWithCount = Domain & {
  _count: { tasks: number; events: number; projects: number; ideas: number };
};

/**
 * Service for managing domains
 */
@Injectable()
export class DomainsService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly activityService: ActivityService
  ) {}

  /**
   * Create a new domain
   */
  async create(
    workspaceId: string,
    userId: string,
    createDomainDto: CreateDomainDto
  ): Promise<DomainWithCount> {
    const domain = await this.prisma.domain.create({
      data: {
        name: createDomainDto.name,
        slug: createDomainDto.slug,
        description: createDomainDto.description ?? null,
        color: createDomainDto.color ?? null,
        icon: createDomainDto.icon ?? null,
        workspace: {
          connect: { id: workspaceId },
        },
        metadata: (createDomainDto.metadata ?? {}) as unknown as Prisma.InputJsonValue,
        sortOrder: 0, // Default to 0, consistent with other services
      },
      include: {
        _count: {
          select: { tasks: true, events: true, projects: true, ideas: true },
        },
      },
    });

    // Log activity
    await this.activityService.logDomainCreated(workspaceId, userId, domain.id, {
      name: domain.name,
    });

    return domain;
  }

  /**
   * Get paginated domains with filters
   */
  async findAll(query: QueryDomainsDto): Promise<{
    data: DomainWithCount[];
    meta: {
      total: number;
      page: number;
      limit: number;
      totalPages: number;
    };
  }> {
    const page = query.page ?? 1;
    const limit = query.limit ?? 50;
    const skip = (page - 1) * limit;

    // Build where clause
    const where: Prisma.DomainWhereInput = query.workspaceId
      ? {
          workspaceId: query.workspaceId,
        }
      : {};

    // Add search filter if provided
    if (query.search) {
      where.OR = [
        { name: { contains: query.search, mode: "insensitive" } },
        { description: { contains: query.search, mode: "insensitive" } },
      ];
    }

    // Execute queries in parallel
    const [data, total] = await Promise.all([
      this.prisma.domain.findMany({
        where,
        include: {
          _count: {
            select: { tasks: true, events: true, projects: true, ideas: true },
          },
        },
        orderBy: {
          sortOrder: "asc",
        },
        skip,
        take: limit,
      }),
      this.prisma.domain.count({ where }),
    ]);

    return {
      data,
      meta: {
        total,
        page,
        limit,
        totalPages: Math.ceil(total / limit),
      },
    };
  }

  /**
   * Get a single domain by ID
   */
  async findOne(id: string, workspaceId: string): Promise<DomainWithCount> {
    const domain = await this.prisma.domain.findUnique({
      where: {
        id,
        workspaceId,
      },
      include: {
        _count: {
          select: { tasks: true, events: true, projects: true, ideas: true },
        },
      },
    });

    if (!domain) {
      throw new NotFoundException(`Domain with ID ${id} not found`);
    }

    return domain;
  }

  /**
   * Update a domain
   */
  async update(
    id: string,
    workspaceId: string,
    userId: string,
    updateDomainDto: UpdateDomainDto
  ): Promise<DomainWithCount> {
    // Verify domain exists
    const existingDomain = await this.prisma.domain.findUnique({
      where: { id, workspaceId },
    });

    if (!existingDomain) {
      throw new NotFoundException(`Domain with ID ${id} not found`);
    }

    // Build update data, only including defined fields
    const updateData: Prisma.DomainUpdateInput = {};
    if (updateDomainDto.name !== undefined) updateData.name = updateDomainDto.name;
    if (updateDomainDto.slug !== undefined) updateData.slug = updateDomainDto.slug;
    if (updateDomainDto.description !== undefined)
      updateData.description = updateDomainDto.description;
    if (updateDomainDto.color !== undefined) updateData.color = updateDomainDto.color;
    if (updateDomainDto.icon !== undefined) updateData.icon = updateDomainDto.icon;
    if (updateDomainDto.metadata !== undefined) {
      updateData.metadata = updateDomainDto.metadata as unknown as Prisma.InputJsonValue;
    }

    const domain = await this.prisma.domain.update({
      where: {
        id,
        workspaceId,
      },
      data: updateData,
      include: {
        _count: {
          select: { tasks: true, events: true, projects: true, ideas: true },
        },
      },
    });

    // Log activity
    await this.activityService.logDomainUpdated(workspaceId, userId, id, {
      changes: updateDomainDto as Prisma.JsonValue,
    });

    return domain;
  }

  /**
   * Delete a domain
   */
  async remove(id: string, workspaceId: string, userId: string): Promise<void> {
    // Verify domain exists
    const domain = await this.prisma.domain.findUnique({
      where: { id, workspaceId },
    });

    if (!domain) {
      throw new NotFoundException(`Domain with ID ${id} not found`);
    }

    await this.prisma.domain.delete({
      where: {
        id,
        workspaceId,
      },
    });

    // Log activity
    await this.activityService.logDomainDeleted(workspaceId, userId, id, {
      name: domain.name,
    });
  }
}
