import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma/prisma.service';
import { ActivityLogService } from '../activity-logs/activity-log.service';
import { ActivityAction, ActivityEntityType, KeywordStatus } from '../../generated/prisma/enums';
import { CreateKeywordDto } from './dto/create-keyword.dto';
import { BulkCreateKeywordsDto } from './dto/bulk-create-keywords.dto';
import { UpdateKeywordDto } from './dto/update-keyword.dto';
import { ServiceLogger } from '../../common/logging/service-logger';

@Injectable()
export class KeywordsService {
  private readonly logger = new ServiceLogger(KeywordsService.name);
  constructor(
    private readonly prisma: PrismaService,
    private readonly activityLogService: ActivityLogService,
  ) {}

  async listClientKeywords(clientId: string, assignedUserId?: string) {
    const client = await this.prisma.client.findFirst({
      where: {
        id: clientId,
        isDeleted: false,
        ...(assignedUserId ? { assignedUserId } : {}),
      },
    });

    if (!client) {
      throw new NotFoundException('Client not found');
    }

    return this.prisma.clientKeyword.findMany({
      where: {
        clientId,
        isDeleted: false,
      },
      include: {
        client: {
          select: {
            id: true,
            companyName: true,
            contactName: true,
          },
        },
      },
      orderBy: { createdAt: 'desc' },
    });
  }

  async listAllKeywords(params: {
    clientId?: string;
    search?: string;
    page?: number;
    limit?: number;
  }, assignedUserId?: string) {
    const whereClause: any = {
      isDeleted: false,
      client: {
        isDeleted: false,
        ...(assignedUserId ? { assignedUserId } : {}),
      },
    };

    if (params.clientId && params.clientId !== 'all') {
      whereClause.clientId = params.clientId;
    }

    if (params.search?.trim()) {
      const term = params.search.trim();
      whereClause.OR = [
        { keyword: { contains: term, mode: 'insensitive' } },
        { client: { contactName: { contains: term, mode: 'insensitive' } } },
        { client: { companyName: { contains: term, mode: 'insensitive' } } },
      ];
    }

    const hasPagination = params.page !== undefined || params.limit !== undefined;

    if (!hasPagination) {
      return this.prisma.clientKeyword.findMany({
        where: whereClause,
        include: {
          client: {
            select: {
              id: true,
              companyName: true,
              contactName: true,
            },
          },
        },
        orderBy: { createdAt: 'desc' },
      });
    }

    const page = Math.max(1, params.page ?? 1);
    const limit = Math.max(1, params.limit ?? 10);
    const total = await this.prisma.clientKeyword.count({ where: whereClause });
    const totalPages = Math.max(1, Math.ceil(total / limit));
    const safePage = Math.min(page, totalPages);

    const items = await this.prisma.clientKeyword.findMany({
      where: whereClause,
      include: {
        client: {
          select: {
            id: true,
            companyName: true,
            contactName: true,
          },
        },
      },
      orderBy: { createdAt: 'desc' },
      skip: (safePage - 1) * limit,
      take: limit,
    });

    return {
      items,
      meta: {
        page: safePage,
        limit,
        total,
        totalPages,
      },
    };
  }

  async createKeyword(
    clientId: string,
    dto: CreateKeywordDto,
    actorUserId?: string,
    assignedUserId?: string,
  ) {
    const client = await this.prisma.client.findFirst({
      where: {
        id: clientId,
        isDeleted: false,
        ...(assignedUserId ? { assignedUserId } : {}),
      },
    });

    if (!client) {
      throw new NotFoundException('Client not found');
    }

    const trimmedKw = dto.keyword.trim();

    // Check for duplicate keyword for this client (case-insensitive)
    const existing = await this.prisma.clientKeyword.findFirst({
      where: {
        clientId,
        keyword: { equals: trimmedKw, mode: 'insensitive' },
      },
    });

    if (existing) {
      if (!existing.isDeleted) {
        throw new ConflictException(`Keyword "${trimmedKw}" already exists for this client.`);
      }
      // If soft-deleted, reactivate it
      const reactivated = await this.prisma.clientKeyword.update({
        where: { id: existing.id },
        data: {
          isDeleted: false,
          status: dto.status ?? KeywordStatus.active,
          keyword: trimmedKw,
        },
      });

      if (actorUserId) {
        const clientName = client.companyName || client.contactName;
        await this.activityLogService.record({
          action: ActivityAction.keyword_created,
          entityType: ActivityEntityType.keyword,
          entityId: reactivated.id,
          title: `Keyword added for ${clientName}`,
          description: `Reactivated target keyword "${reactivated.keyword}" for ${clientName}.`,
          actorUserId,
          clientId,
        });
      }
      this.logger.log('keyword.reactivated', { keywordId: reactivated.id, clientId, actorUserId });
      return reactivated;
    }

    const keyword = await this.prisma.clientKeyword.create({
      data: {
        clientId,
        keyword: trimmedKw,
        status: dto.status ?? KeywordStatus.active,
      },
    });

    if (actorUserId) {
      const clientName = client.companyName || client.contactName;
      await this.activityLogService.record({
        action: ActivityAction.keyword_created,
        entityType: ActivityEntityType.keyword,
        entityId: keyword.id,
        title: `Keyword added for ${clientName}`,
        description: `Added target keyword "${keyword.keyword}" for ${clientName}.`,
        actorUserId,
        clientId,
      });
    }
    this.logger.log('keyword.created', { keywordId: keyword.id, clientId, actorUserId });

    return keyword;
  }

  async bulkCreateKeywords(
    clientId: string,
    dto: BulkCreateKeywordsDto,
    actorUserId?: string,
    assignedUserId?: string,
  ) {
    const client = await this.prisma.client.findFirst({
      where: {
        id: clientId,
        isDeleted: false,
        ...(assignedUserId ? { assignedUserId } : {}),
      },
    });

    if (!client) {
      throw new NotFoundException('Client not found');
    }

    const uniqueKeywords = Array.from(
      new Set(
        dto.keywords
          .map((k) => k.trim())
          .filter((k) => k.length > 0),
      ),
    );

    if (uniqueKeywords.length === 0) {
      return [];
    }

    // Check existing records for this client
    const existingRecords = await this.prisma.clientKeyword.findMany({
      where: {
        clientId,
        OR: uniqueKeywords.map((kw) => ({
          keyword: { equals: kw, mode: 'insensitive' },
        })),
      },
    });

    const activeSet = new Set(
      existingRecords.filter((r) => !r.isDeleted).map((r) => r.keyword.toLowerCase()),
    );

    const deletedMap = new Map(
      existingRecords.filter((r) => r.isDeleted).map((r) => [r.keyword.toLowerCase(), r]),
    );

    // Filter out keywords that are already active
    const keywordsToProcess = uniqueKeywords.filter((kw) => !activeSet.has(kw.toLowerCase()));

    if (keywordsToProcess.length === 0) {
      throw new ConflictException('All provided keywords already exist for this client.');
    }

    const created = await this.prisma.$transaction(
      keywordsToProcess.map((kw) => {
        const deletedRecord = deletedMap.get(kw.toLowerCase());
        if (deletedRecord) {
          return this.prisma.clientKeyword.update({
            where: { id: deletedRecord.id },
            data: {
              isDeleted: false,
              status: KeywordStatus.active,
              keyword: kw,
            },
          });
        }
        return this.prisma.clientKeyword.create({
          data: {
            clientId,
            keyword: kw,
            status: KeywordStatus.active,
          },
        });
      }),
    );

    if (actorUserId) {
      const clientName = client.companyName || client.contactName;
      const kwNames = created.map((k) => `"${k.keyword}"`);
      const sampleList =
        kwNames.length <= 5
          ? kwNames.join(', ')
          : `${kwNames.slice(0, 5).join(', ')} and ${kwNames.length - 5} more`;

      await this.activityLogService.record({
        action: ActivityAction.keyword_created,
        entityType: ActivityEntityType.keyword,
        entityId: clientId,
        title: `Bulk keywords added for ${clientName}`,
        description: `Added ${created.length} target keyword(s) for ${clientName}: ${sampleList}.`,
        actorUserId,
        clientId,
      });
    }
    this.logger.log('keyword.bulk_created', { clientId, actorUserId, count: created.length });

    return created;
  }

  async updateKeyword(
    id: string,
    dto: UpdateKeywordDto,
    actorUserId?: string,
    assignedUserId?: string,
  ) {
    const keyword = await this.prisma.clientKeyword.findFirst({
      where: {
        id,
        isDeleted: false,
        client: {
          isDeleted: false,
          ...(assignedUserId ? { assignedUserId } : {}),
        },
      },
      include: {
        client: {
          select: {
            companyName: true,
            contactName: true,
          },
        },
      },
    });

    if (!keyword) {
      throw new NotFoundException('Keyword not found');
    }

    if (dto.keyword !== undefined) {
      const trimmedKw = dto.keyword.trim();
      const duplicate = await this.prisma.clientKeyword.findFirst({
        where: {
          clientId: keyword.clientId,
          id: { not: id },
          isDeleted: false,
          keyword: { equals: trimmedKw, mode: 'insensitive' },
        },
      });

      if (duplicate) {
        throw new ConflictException(`Keyword "${trimmedKw}" already exists for this client.`);
      }
    }

    const updated = await this.prisma.clientKeyword.update({
      where: { id },
      data: {
        ...(dto.keyword !== undefined && { keyword: dto.keyword.trim() }),
        ...(dto.status !== undefined && { status: dto.status }),
      },
    });

    if (actorUserId) {
      const clientName =
        keyword.client?.companyName || keyword.client?.contactName || 'Client';
      const changes: string[] = [];
      if (
        dto.keyword !== undefined &&
        keyword.keyword !== updated.keyword
      ) {
        changes.push(
          `keyword name changed from "${keyword.keyword}" to "${updated.keyword}"`,
        );
      }
      if (dto.status !== undefined && keyword.status !== updated.status) {
        changes.push(
          `status changed from "${keyword.status}" to "${updated.status}"`,
        );
      }

      const description =
        changes.length > 0
          ? `${clientName}: ${changes.join('; ')}.`
          : `${clientName}: Keyword "${updated.keyword}" was updated.`;

      await this.activityLogService.record({
        action: ActivityAction.keyword_updated,
        entityType: ActivityEntityType.keyword,
        entityId: id,
        title: `Keyword updated for ${clientName}`,
        description,
        actorUserId,
        clientId: keyword.clientId,
      });
    }
    this.logger.log('keyword.updated', { keywordId: updated.id, clientId: keyword.clientId, actorUserId });

    return updated;
  }
}
