import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import {
  UserTagRepository,
  UserTagWithAssignment,
} from '../../../domain/repositories/user-tag.repository';
import {
  UserTagEntity,
  UserTagAssignmentEntity,
  TagCategoryEntity,
  UserTagLogEntity,
  TagType,
  TagValueType,
  TagAction,
} from '../../../domain/entities/user-tag.entity';
import {
  Prisma,
  TagType as PrismaTagType,
  TagValueType as PrismaTagValueType,
  TagAction as PrismaTagAction,
} from '@prisma/client';

@Injectable()
export class UserTagRepositoryImpl implements UserTagRepository {
  private readonly logger = new Logger(UserTagRepositoryImpl.name);

  constructor(private readonly prisma: PrismaService) {}

  // =====================
  // 标签分类 CRUD
  // =====================

  async saveCategory(category: TagCategoryEntity): Promise<TagCategoryEntity> {
    const saved = await this.prisma.tagCategory.upsert({
      where: { id: category.id },
      create: {
        id: category.id,
        code: category.code,
        name: category.name,
        description: category.description,
        sortOrder: category.sortOrder,
        isEnabled: category.isEnabled,
        createdAt: category.createdAt,
        updatedAt: category.updatedAt,
      },
      update: {
        name: category.name,
        description: category.description,
        sortOrder: category.sortOrder,
        isEnabled: category.isEnabled,
        updatedAt: category.updatedAt,
      },
    });
    return this.mapCategoryToDomain(saved);
  }

  async findCategoryById(id: string): Promise<TagCategoryEntity | null> {
    const category = await this.prisma.tagCategory.findUnique({ where: { id } });
    return category ? this.mapCategoryToDomain(category) : null;
  }

  async findCategoryByCode(code: string): Promise<TagCategoryEntity | null> {
    const category = await this.prisma.tagCategory.findUnique({ where: { code } });
    return category ? this.mapCategoryToDomain(category) : null;
  }

  async findAllCategories(params?: { isEnabled?: boolean }): Promise<TagCategoryEntity[]> {
    const categories = await this.prisma.tagCategory.findMany({
      where: {
        ...(params?.isEnabled !== undefined && { isEnabled: params.isEnabled }),
      },
      orderBy: [{ sortOrder: 'asc' }, { createdAt: 'asc' }],
    });
    return categories.map((c) => this.mapCategoryToDomain(c));
  }

  async deleteCategory(id: string): Promise<void> {
    await this.prisma.tagCategory.delete({ where: { id } });
  }

  // =====================
  // 标签定义 CRUD
  // =====================

  async saveTag(tag: UserTagEntity): Promise<UserTagEntity> {
    // 处理 JSON 字段的 null 值
    const enumValuesData = tag.enumValues === null
      ? Prisma.JsonNull
      : tag.enumValues ?? Prisma.JsonNull;

    const saved = await this.prisma.userTag.upsert({
      where: { id: tag.id },
      create: {
        id: tag.id,
        categoryId: tag.categoryId,
        code: tag.code,
        name: tag.name,
        description: tag.description,
        color: tag.color,
        type: tag.type as PrismaTagType,
        valueType: tag.valueType as PrismaTagValueType,
        enumValues: enumValuesData,
        ruleId: tag.ruleId,
        isAdvertisable: tag.isAdvertisable,
        estimatedUsers: tag.estimatedUsers,
        isEnabled: tag.isEnabled,
        sortOrder: tag.sortOrder,
        createdAt: tag.createdAt,
        updatedAt: tag.updatedAt,
      },
      update: {
        categoryId: tag.categoryId,
        name: tag.name,
        description: tag.description,
        color: tag.color,
        type: tag.type as PrismaTagType,
        valueType: tag.valueType as PrismaTagValueType,
        enumValues: enumValuesData,
        ruleId: tag.ruleId,
        isAdvertisable: tag.isAdvertisable,
        estimatedUsers: tag.estimatedUsers,
        isEnabled: tag.isEnabled,
        sortOrder: tag.sortOrder,
        updatedAt: tag.updatedAt,
      },
    });
    return this.mapTagToDomain(saved);
  }

  async findTagById(id: string): Promise<UserTagEntity | null> {
    const tag = await this.prisma.userTag.findUnique({ where: { id } });
    return tag ? this.mapTagToDomain(tag) : null;
  }

  async findTagByCode(code: string): Promise<UserTagEntity | null> {
    const tag = await this.prisma.userTag.findUnique({ where: { code } });
    return tag ? this.mapTagToDomain(tag) : null;
  }

  async findAllTags(params?: {
    categoryId?: string;
    type?: TagType;
    isEnabled?: boolean;
    isAdvertisable?: boolean;
    limit?: number;
    offset?: number;
  }): Promise<UserTagEntity[]> {
    const tags = await this.prisma.userTag.findMany({
      where: {
        ...(params?.categoryId && { categoryId: params.categoryId }),
        ...(params?.type && { type: params.type as PrismaTagType }),
        ...(params?.isEnabled !== undefined && { isEnabled: params.isEnabled }),
        ...(params?.isAdvertisable !== undefined && { isAdvertisable: params.isAdvertisable }),
      },
      orderBy: [{ sortOrder: 'asc' }, { createdAt: 'desc' }],
      take: params?.limit ?? 100,
      skip: params?.offset ?? 0,
    });
    return tags.map((t) => this.mapTagToDomain(t));
  }

  async findEnabledTags(): Promise<UserTagEntity[]> {
    return this.findAllTags({ isEnabled: true });
  }

  async findAutoTags(): Promise<UserTagEntity[]> {
    const tags = await this.prisma.userTag.findMany({
      where: {
        type: 'AUTO',
        isEnabled: true,
        ruleId: { not: null },
      },
      orderBy: { sortOrder: 'asc' },
    });
    return tags.map((t) => this.mapTagToDomain(t));
  }

  async findAdvertisableTags(): Promise<UserTagEntity[]> {
    return this.findAllTags({ isEnabled: true, isAdvertisable: true });
  }

  async countTags(params?: {
    categoryId?: string;
    type?: TagType;
    isEnabled?: boolean;
  }): Promise<number> {
    return this.prisma.userTag.count({
      where: {
        ...(params?.categoryId && { categoryId: params.categoryId }),
        ...(params?.type && { type: params.type as PrismaTagType }),
        ...(params?.isEnabled !== undefined && { isEnabled: params.isEnabled }),
      },
    });
  }

  async deleteTag(id: string): Promise<void> {
    await this.prisma.userTag.delete({ where: { id } });
  }

  async updateEstimatedUsers(tagId: string, count: number): Promise<void> {
    await this.prisma.userTag.update({
      where: { id: tagId },
      data: { estimatedUsers: count },
    });
  }

  // =====================
  // 用户-标签关联
  // =====================

  async saveAssignment(assignment: UserTagAssignmentEntity): Promise<UserTagAssignmentEntity> {
    const saved = await this.prisma.userTagAssignment.upsert({
      where: {
        accountSequence_tagId: {
          accountSequence: assignment.accountSequence,
          tagId: assignment.tagId,
        },
      },
      create: {
        id: assignment.id,
        accountSequence: assignment.accountSequence,
        tagId: assignment.tagId,
        value: assignment.value,
        assignedAt: assignment.assignedAt,
        assignedBy: assignment.assignedBy,
        expiresAt: assignment.expiresAt,
        source: assignment.source,
      },
      update: {
        value: assignment.value,
        assignedAt: assignment.assignedAt,
        assignedBy: assignment.assignedBy,
        expiresAt: assignment.expiresAt,
        source: assignment.source,
      },
    });
    return this.mapAssignmentToDomain(saved);
  }

  async saveAssignments(assignments: UserTagAssignmentEntity[]): Promise<void> {
    if (assignments.length === 0) return;

    await this.prisma.$transaction(
      assignments.map((a) =>
        this.prisma.userTagAssignment.upsert({
          where: {
            accountSequence_tagId: {
              accountSequence: a.accountSequence,
              tagId: a.tagId,
            },
          },
          create: {
            id: a.id,
            accountSequence: a.accountSequence,
            tagId: a.tagId,
            value: a.value,
            assignedAt: a.assignedAt,
            assignedBy: a.assignedBy,
            expiresAt: a.expiresAt,
            source: a.source,
          },
          update: {
            value: a.value,
            assignedAt: a.assignedAt,
            assignedBy: a.assignedBy,
            expiresAt: a.expiresAt,
            source: a.source,
          },
        }),
      ),
    );
  }

  async findUserTags(accountSequence: string): Promise<UserTagWithAssignment[]> {
    const now = new Date();
    const assignments = await this.prisma.userTagAssignment.findMany({
      where: {
        accountSequence,
        OR: [{ expiresAt: null }, { expiresAt: { gt: now } }],
      },
      include: { tag: true },
      orderBy: { assignedAt: 'desc' },
    });

    return assignments
      .filter((a) => a.tag.isEnabled)
      .map((a) => ({
        tag: this.mapTagToDomain(a.tag),
        assignment: this.mapAssignmentToDomain(a),
      }));
  }

  async findUserTagIds(accountSequence: string): Promise<string[]> {
    const now = new Date();
    const assignments = await this.prisma.userTagAssignment.findMany({
      where: {
        accountSequence,
        OR: [{ expiresAt: null }, { expiresAt: { gt: now } }],
        tag: { isEnabled: true },
      },
      select: { tagId: true },
    });
    return assignments.map((a) => a.tagId);
  }

  async findUsersByTagId(
    tagId: string,
    params?: { value?: string; limit?: number; offset?: number },
  ): Promise<UserTagAssignmentEntity[]> {
    const now = new Date();
    const assignments = await this.prisma.userTagAssignment.findMany({
      where: {
        tagId,
        OR: [{ expiresAt: null }, { expiresAt: { gt: now } }],
        ...(params?.value !== undefined && { value: params.value }),
      },
      orderBy: { assignedAt: 'desc' },
      take: params?.limit ?? 100,
      skip: params?.offset ?? 0,
    });
    return assignments.map((a) => this.mapAssignmentToDomain(a));
  }

  async findUsersByTagCode(
    tagCode: string,
    params?: { value?: string; limit?: number; offset?: number },
  ): Promise<UserTagAssignmentEntity[]> {
    const now = new Date();
    const assignments = await this.prisma.userTagAssignment.findMany({
      where: {
        tag: { code: tagCode },
        OR: [{ expiresAt: null }, { expiresAt: { gt: now } }],
        ...(params?.value !== undefined && { value: params.value }),
      },
      orderBy: { assignedAt: 'desc' },
      take: params?.limit ?? 100,
      skip: params?.offset ?? 0,
    });
    return assignments.map((a) => this.mapAssignmentToDomain(a));
  }

  async countUsersByTagId(tagId: string, value?: string): Promise<number> {
    const now = new Date();
    return this.prisma.userTagAssignment.count({
      where: {
        tagId,
        OR: [{ expiresAt: null }, { expiresAt: { gt: now } }],
        ...(value !== undefined && { value }),
      },
    });
  }

  async hasTag(accountSequence: string, tagId: string): Promise<boolean> {
    const now = new Date();
    const count = await this.prisma.userTagAssignment.count({
      where: {
        accountSequence,
        tagId,
        OR: [{ expiresAt: null }, { expiresAt: { gt: now } }],
        tag: { isEnabled: true },
      },
    });
    return count > 0;
  }

  async hasAnyTag(accountSequence: string, tagIds: string[]): Promise<boolean> {
    if (tagIds.length === 0) return false;
    const now = new Date();
    const count = await this.prisma.userTagAssignment.count({
      where: {
        accountSequence,
        tagId: { in: tagIds },
        OR: [{ expiresAt: null }, { expiresAt: { gt: now } }],
        tag: { isEnabled: true },
      },
    });
    return count > 0;
  }

  async hasAllTags(accountSequence: string, tagIds: string[]): Promise<boolean> {
    if (tagIds.length === 0) return true;
    const now = new Date();
    const count = await this.prisma.userTagAssignment.count({
      where: {
        accountSequence,
        tagId: { in: tagIds },
        OR: [{ expiresAt: null }, { expiresAt: { gt: now } }],
        tag: { isEnabled: true },
      },
    });
    return count === tagIds.length;
  }

  async getAssignment(
    accountSequence: string,
    tagId: string,
  ): Promise<UserTagAssignmentEntity | null> {
    const assignment = await this.prisma.userTagAssignment.findUnique({
      where: {
        accountSequence_tagId: { accountSequence, tagId },
      },
    });
    return assignment ? this.mapAssignmentToDomain(assignment) : null;
  }

  async deleteAssignment(accountSequence: string, tagId: string): Promise<void> {
    await this.prisma.userTagAssignment.deleteMany({
      where: { accountSequence, tagId },
    });
  }

  async deleteAutoAssignments(accountSequence: string): Promise<void> {
    await this.prisma.userTagAssignment.deleteMany({
      where: {
        accountSequence,
        source: { startsWith: 'rule:' },
      },
    });
  }

  async deleteAssignmentsByTagId(tagId: string): Promise<void> {
    await this.prisma.userTagAssignment.deleteMany({
      where: { tagId },
    });
  }

  async deleteAssignmentsBySource(source: string): Promise<number> {
    const result = await this.prisma.userTagAssignment.deleteMany({
      where: { source },
    });
    return result.count;
  }

  async cleanExpiredAssignments(): Promise<number> {
    const now = new Date();
    const result = await this.prisma.userTagAssignment.deleteMany({
      where: {
        expiresAt: { lt: now },
      },
    });
    if (result.count > 0) {
      this.logger.log(`Cleaned ${result.count} expired tag assignments`);
    }
    return result.count;
  }

  // =====================
  // 标签变更日志
  // =====================

  async saveLog(log: UserTagLogEntity): Promise<void> {
    await this.prisma.userTagLog.create({
      data: {
        id: log.id,
        accountSequence: log.accountSequence,
        tagCode: log.tagCode,
        action: log.action as PrismaTagAction,
        oldValue: log.oldValue,
        newValue: log.newValue,
        reason: log.reason,
        operatorId: log.operatorId,
        createdAt: log.createdAt,
      },
    });
  }

  async saveLogs(logs: UserTagLogEntity[]): Promise<void> {
    if (logs.length === 0) return;
    await this.prisma.userTagLog.createMany({
      data: logs.map((log) => ({
        id: log.id,
        accountSequence: log.accountSequence,
        tagCode: log.tagCode,
        action: log.action as PrismaTagAction,
        oldValue: log.oldValue,
        newValue: log.newValue,
        reason: log.reason,
        operatorId: log.operatorId,
        createdAt: log.createdAt,
      })),
    });
  }

  async findLogs(params: {
    accountSequence?: string;
    tagCode?: string;
    action?: TagAction;
    startDate?: Date;
    endDate?: Date;
    limit?: number;
    offset?: number;
  }): Promise<UserTagLogEntity[]> {
    const logs = await this.prisma.userTagLog.findMany({
      where: {
        ...(params.accountSequence && { accountSequence: params.accountSequence }),
        ...(params.tagCode && { tagCode: params.tagCode }),
        ...(params.action && { action: params.action as PrismaTagAction }),
        ...(params.startDate || params.endDate
          ? {
              createdAt: {
                ...(params.startDate && { gte: params.startDate }),
                ...(params.endDate && { lte: params.endDate }),
              },
            }
          : {}),
      },
      orderBy: { createdAt: 'desc' },
      take: params.limit ?? 100,
      skip: params.offset ?? 0,
    });
    return logs.map((log) => this.mapLogToDomain(log));
  }

  async countLogs(params: {
    accountSequence?: string;
    tagCode?: string;
    action?: TagAction;
    startDate?: Date;
    endDate?: Date;
  }): Promise<number> {
    return this.prisma.userTagLog.count({
      where: {
        ...(params.accountSequence && { accountSequence: params.accountSequence }),
        ...(params.tagCode && { tagCode: params.tagCode }),
        ...(params.action && { action: params.action as PrismaTagAction }),
        ...(params.startDate || params.endDate
          ? {
              createdAt: {
                ...(params.startDate && { gte: params.startDate }),
                ...(params.endDate && { lte: params.endDate }),
              },
            }
          : {}),
      },
    });
  }

  // =====================
  // Private Methods
  // =====================

  private mapCategoryToDomain(prisma: {
    id: string;
    code: string;
    name: string;
    description: string | null;
    sortOrder: number;
    isEnabled: boolean;
    createdAt: Date;
    updatedAt: Date;
  }): TagCategoryEntity {
    return new TagCategoryEntity(
      prisma.id,
      prisma.code,
      prisma.name,
      prisma.description,
      prisma.sortOrder,
      prisma.isEnabled,
      prisma.createdAt,
      prisma.updatedAt,
    );
  }

  private mapTagToDomain(prisma: {
    id: string;
    categoryId: string | null;
    code: string;
    name: string;
    description: string | null;
    color: string | null;
    type: PrismaTagType;
    valueType: PrismaTagValueType;
    enumValues: unknown;
    ruleId: string | null;
    isAdvertisable: boolean;
    estimatedUsers: number | null;
    isEnabled: boolean;
    sortOrder: number;
    createdAt: Date;
    updatedAt: Date;
  }): UserTagEntity {
    return new UserTagEntity(
      prisma.id,
      prisma.categoryId,
      prisma.code,
      prisma.name,
      prisma.description,
      prisma.color,
      prisma.type as TagType,
      prisma.valueType as TagValueType,
      prisma.enumValues as string[] | null,
      prisma.ruleId,
      prisma.isAdvertisable,
      prisma.estimatedUsers,
      prisma.isEnabled,
      prisma.sortOrder,
      prisma.createdAt,
      prisma.updatedAt,
    );
  }

  private mapAssignmentToDomain(prisma: {
    id: string;
    accountSequence: string;
    tagId: string;
    value: string | null;
    assignedAt: Date;
    assignedBy: string | null;
    expiresAt: Date | null;
    source: string | null;
  }): UserTagAssignmentEntity {
    return new UserTagAssignmentEntity(
      prisma.id,
      prisma.accountSequence,
      prisma.tagId,
      prisma.value,
      prisma.assignedAt,
      prisma.assignedBy,
      prisma.expiresAt,
      prisma.source,
    );
  }

  private mapLogToDomain(prisma: {
    id: string;
    accountSequence: string;
    tagCode: string;
    action: PrismaTagAction;
    oldValue: string | null;
    newValue: string | null;
    reason: string | null;
    operatorId: string | null;
    createdAt: Date;
  }): UserTagLogEntity {
    return new UserTagLogEntity(
      prisma.id,
      prisma.accountSequence,
      prisma.tagCode,
      prisma.action as TagAction,
      prisma.oldValue,
      prisma.newValue,
      prisma.reason,
      prisma.operatorId,
      prisma.createdAt,
    );
  }
}
