import { Injectable } from '@nestjs/common';

import { PrismaService } from '../../common/prisma/prisma.service';
import { Prisma } from '../../generated/prisma/client';
import {
  type ActivityAction,
  type ActivityEntityType,
  type ActivityLog,
  type Client,
  type Report,
  type User,
} from '../../generated/prisma/client';
import { ListActivityLogsQueryDto } from './dto/list-activity-logs-query.dto';
import { ServiceLogger } from '../../common/logging/service-logger';

export type ActivityLogActor = Pick<
  User,
  'id' | 'name' | 'email' | 'role' | 'status'
>;

export type ActivityLogClient = Pick<
  Client,
  'id' | 'companyName' | 'contactName' | 'email' | 'website' | 'dashboardSlug'
>;

export type ActivityLogReport = Pick<
  Report,
  'id' | 'clientId' | 'version' | 'reportStartDate' | 'reportEndDate'
>;

export type ActivityLogRecord = Pick<
  ActivityLog,
  | 'id'
  | 'action'
  | 'entityType'
  | 'entityId'
  | 'title'
  | 'description'
  | 'createdAt'
> & {
  actorUser: ActivityLogActor | null;
  targetUser: ActivityLogActor | null;
  client: ActivityLogClient | null;
  report: ActivityLogReport | null;
};

export type ActivityLogSummary = {
  total: number;
  auth: number;
  user: number;
  client: number;
  report: number;
  uniqueActors: number;
  latestAt: Date | null;
};

export type ActivityLogListResponse = {
  items: ActivityLogRecord[];
  meta: {
    page: number;
    limit: number;
    total: number;
    totalPages: number;
  };
  summary: ActivityLogSummary;
};

export interface RecordActivityLogInput {
  action: ActivityAction;
  entityType: ActivityEntityType;
  entityId?: string | null;
  title: string;
  description: string;
  metadata?: Prisma.InputJsonValue | null;
  actorUserId?: string | null;
  clientId?: string | null;
  reportId?: string | null;
}

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

  async listActivityLogs(
    query: ListActivityLogsQueryDto,
    actorUserId?: string,
  ): Promise<ActivityLogListResponse> {
    const page = query.page ?? 1;
    const limit = query.limit ?? 8;
    const where = {
      ...this.buildWhere(query),
      ...(actorUserId ? { actorUserId } : {}),
    };

    const [total, auth, user, client, report, latest, uniqueActors] =
      await Promise.all([
        this.prisma.activityLog.count({ where }),
        this.prisma.activityLog.count({
          where: {
            ...where,
            entityType: 'auth',
          },
        }),
        this.prisma.activityLog.count({
          where: {
            ...where,
            entityType: 'user',
          },
        }),
        this.prisma.activityLog.count({
          where: {
            ...where,
            entityType: 'client',
          },
        }),
        this.prisma.activityLog.count({
          where: {
            ...where,
            entityType: 'report',
          },
        }),
        this.prisma.activityLog.findFirst({
          where,
          orderBy: {
            createdAt: 'desc',
          },
          select: {
            createdAt: true,
          },
        }),
        this.prisma.activityLog.findMany({
          where: {
            ...where,
            actorUserId: {
              not: null,
            },
          },
          distinct: ['actorUserId'],
          select: {
            actorUserId: true,
          },
        }),
      ]);

    const totalPages = Math.max(1, Math.ceil(total / limit));
    const safePage = Math.min(page, totalPages);

    const items = await this.prisma.activityLog.findMany({
      where,
      orderBy: {
        createdAt: 'desc',
      },
      skip: (safePage - 1) * limit,
      take: limit,
      include: {
        actorUser: {
          select: {
            id: true,
            name: true,
            email: true,
            role: true,
            status: true,
          },
        },
        client: {
          select: {
            id: true,
            companyName: true,
            contactName: true,
            email: true,
            website: true,
            dashboardSlug: true,
          },
        },
        report: {
          select: {
            id: true,
            clientId: true,
            version: true,
            reportStartDate: true,
            reportEndDate: true,
          },
        },
      },
    });

    const targetUserIds = [
      ...new Set(
        items
          .filter((item) => item.entityType === 'user' && item.entityId)
          .map((item) => item.entityId as string),
      ),
    ];

    const targetUsers =
      targetUserIds.length === 0
        ? []
        : await this.prisma.user.findMany({
            where: {
              id: {
                in: targetUserIds,
              },
            },
            select: {
              id: true,
              name: true,
              email: true,
              role: true,
              status: true,
            },
          });

    const targetUserMap = new Map(targetUsers.map((user) => [user.id, user]));

    return {
      items: items.map((item) => {
        const { metadata: _metadata, ...activityLog } = item;

        return {
          ...activityLog,
          targetUser:
            item.entityType === 'user' && item.entityId
              ? (targetUserMap.get(item.entityId) ?? null)
              : null,
        };
      }),
      meta: {
        page: safePage,
        limit,
        total,
        totalPages,
      },
      summary: {
        total,
        auth,
        user,
        client,
        report,
        uniqueActors: uniqueActors.length,
        latestAt: latest?.createdAt ?? null,
      },
    };
  }

  async record(input: RecordActivityLogInput) {
    const metadata =
      input.metadata === undefined
        ? undefined
        : input.metadata === null
          ? Prisma.JsonNull
          : input.metadata;

    try {
      const record = await this.prisma.activityLog.create({
        data: {
          action: input.action,
          entityType: input.entityType,
          entityId: input.entityId ?? null,
          title: input.title,
          description: input.description,
          metadata,
          actorUserId: input.actorUserId ?? null,
          clientId: input.clientId ?? null,
          reportId: input.reportId ?? null,
        },
      });
      return record;
    } catch (error) {
      this.logger.error('activity_log.record_failed', error, {
        action: input.action, entityType: input.entityType,
        entityId: input.entityId, actorUserId: input.actorUserId,
      });
      throw error;
    }
  }

  private buildWhere(
    query: ListActivityLogsQueryDto,
  ): Prisma.ActivityLogWhereInput {
    const search = query.search?.trim();

    return {
      ...(query.clientId ? { clientId: query.clientId } : {}),
      ...(query.action ? { action: query.action } : {}),
      ...(search
        ? {
            OR: [
              { title: { contains: search, mode: 'insensitive' } },
              { description: { contains: search, mode: 'insensitive' } },
              { entityId: { contains: search, mode: 'insensitive' } },
              {
                actorUser: {
                  is: {
                    OR: [
                      { name: { contains: search, mode: 'insensitive' } },
                      { email: { contains: search, mode: 'insensitive' } },
                    ],
                  },
                },
              },
              {
                client: {
                  is: {
                    OR: [
                      {
                        companyName: { contains: search, mode: 'insensitive' },
                      },
                      { website: { contains: search, mode: 'insensitive' } },
                      {
                        dashboardSlug: {
                          contains: search,
                          mode: 'insensitive',
                        },
                      },
                    ],
                  },
                },
              },
              {
                report: {
                  is: {
                    OR: [
                      { id: { contains: search, mode: 'insensitive' } },
                      { notes: { contains: search, mode: 'insensitive' } },
                    ],
                  },
                },
              },
            ],
          }
        : {}),
    };
  }
}
