import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { Prisma, FormInstanceStatus } from '@prisma/client';
import {
  AdminAnalyticsQueryDto,
  AdminInstancesQueryDto,
} from './dto/approval.dto';
import {
  AdminAnalyticsResponse,
  AdminInstanceItem,
  AdminInstancesResponse,
  UserInfo,
} from './dto/approval-response.dto';

const FORM_BUSINESS_TYPE = 'FORM_INSTANCE';
const APPROVAL_STATUSES = new Set([
  'RUNNING',
  'SUSPENDED',
  'APPROVED',
  'REJECTED',
  'WITHDRAWN',
  'TERMINATED',
  'FAILED',
]);
const FORM_STATUSES = new Set([
  'DRAFT',
  'SUBMITTED',
  'PENDING_APPROVAL',
  'APPROVED',
  'REJECTED',
  'WITHDRAWN',
  'CANCELLED',
]);

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

  constructor(private readonly prisma: PrismaService) {}

  async getAdminAnalytics(query: AdminAnalyticsQueryDto): Promise<AdminAnalyticsResponse> {
    if (query.businessType && query.businessType !== FORM_BUSINESS_TYPE) {
      return {
        summary: {
          totalSubmissions: 0,
          approvalRate: 0,
          rejectRate: 0,
          avgDurationMs: 0,
          runningCount: 0,
        },
        trends: [],
        distribution: [],
      };
    }

    const where = await this.buildFormInstanceWhere(query);
    const instances = await this.prisma.formInstance.findMany({
      where,
      select: {
        id: true,
        formKey: true,
        status: true,
        approvalStatus: true,
        approvalInstanceId: true,
        submittedAt: true,
        createdAt: true,
        approvalStartTime: true,
        approvalEndTime: true,
        updatedAt: true,
        definition: {
          select: {
            key: true,
            name: true,
            requiresApproval: true,
            approvalProcessKey: true,
          },
        },
      },
    });

    const summary = {
      totalSubmissions: 0,
      approvedCount: 0,
      rejectedCount: 0,
      runningCount: 0,
      durationTotal: 0,
      durationCount: 0,
    };

    const trendMap = new Map<string, { submissions: number; approvals: number; rejections: number }>();
    const distributionMap = new Map<string, { formKey: string; formName: string; count: number }>();

    for (const instance of instances) {
      if (instance.status === 'DRAFT') {
        continue;
      }

      const status = this.resolveUnifiedStatus(instance.approvalInstanceId, instance.approvalStatus, instance.status);
      const submittedAt = instance.submittedAt || instance.createdAt;
      const dateKey = submittedAt.toISOString().slice(0, 10);

      summary.totalSubmissions += 1;

      const trend = trendMap.get(dateKey) || { submissions: 0, approvals: 0, rejections: 0 };
      trend.submissions += 1;

      if (this.isApproved(status)) {
        summary.approvedCount += 1;
        trend.approvals += 1;
      } else if (this.isRejected(status)) {
        summary.rejectedCount += 1;
        trend.rejections += 1;
      } else if (this.isRunning(status)) {
        summary.runningCount += 1;
      }

      trendMap.set(dateKey, trend);

      const formKey = instance.definition?.key || instance.formKey || 'unknown';
      const formName = instance.definition?.name || formKey;
      const distribution = distributionMap.get(formKey) || { formKey, formName, count: 0 };
      distribution.count += 1;
      distributionMap.set(formKey, distribution);

      const durationMs = this.resolveDuration(instance);
      if (durationMs !== undefined) {
        summary.durationTotal += durationMs;
        summary.durationCount += 1;
      }
    }

    const finishedTotal = summary.approvedCount + summary.rejectedCount;
    const approvalRate = finishedTotal > 0 ? summary.approvedCount / finishedTotal : 0;
    const rejectRate = finishedTotal > 0 ? summary.rejectedCount / finishedTotal : 0;
    const avgDurationMs = summary.durationCount > 0 ? Math.round(summary.durationTotal / summary.durationCount) : 0;

    return {
      summary: {
        totalSubmissions: summary.totalSubmissions,
        approvalRate: Number((approvalRate * 100).toFixed(2)),
        rejectRate: Number((rejectRate * 100).toFixed(2)),
        avgDurationMs,
        runningCount: summary.runningCount,
      },
      trends: Array.from(trendMap.entries())
        .sort(([a], [b]) => a.localeCompare(b))
        .map(([date, trend]) => ({
          date,
          submissions: trend.submissions,
          approvals: trend.approvals,
          rejections: trend.rejections,
        })),
      distribution: Array.from(distributionMap.values()).sort((a, b) => b.count - a.count),
    };
  }

  async getAdminInstances(query: AdminInstancesQueryDto): Promise<AdminInstancesResponse> {
    if (query.businessType && query.businessType !== FORM_BUSINESS_TYPE) {
      return {
        items: [],
        total: 0,
        page: query.page || 1,
        limit: query.limit || 20,
        totalPages: 0,
        hasNext: false,
        hasPrev: false,
      };
    }

    const where = await this.buildFormInstanceWhere(query);
    const page = query.page || 1;
    const limit = query.limit || 20;

    const [total, instances] = await Promise.all([
      this.prisma.formInstance.count({ where }),
      this.prisma.formInstance.findMany({
        where,
        orderBy: [{ submittedAt: 'desc' }, { createdAt: 'desc' }],
        skip: (page - 1) * limit,
        take: limit,
        include: {
          definition: {
            select: { key: true, name: true, requiresApproval: true, approvalProcessKey: true },
          },
          submitter: {
            select: { id: true, displayName: true, avatar: true, email: true },
          },
          creator: {
            select: { id: true, displayName: true, avatar: true, email: true },
          },
        },
      }),
    ]);

    const approvalInstanceIds = instances
      .map((item) => item.approvalInstanceId)
      .filter((id): id is string => Boolean(id));

    const [approvalInstances, activeNodes] = await Promise.all([
      approvalInstanceIds.length
        ? this.prisma.approvalInstance.findMany({
            where: { id: { in: approvalInstanceIds } },
            select: {
              id: true,
              status: true,
              currentNodeId: true,
              startTime: true,
              endTime: true,
            },
          })
        : [],
      approvalInstanceIds.length
        ? this.prisma.approvalNodeInstance.findMany({
            where: {
              instanceId: { in: approvalInstanceIds },
              status: { in: ['ACTIVE', 'PENDING'] },
            },
            select: {
              instanceId: true,
              nodeId: true,
              nodeName: true,
              startTime: true,
            },
            orderBy: { startTime: 'desc' },
          })
        : [],
    ]);

    const approvalMap = new Map(approvalInstances.map((item) => [item.id, item]));
    const nodeMap = new Map<string, string>();
    for (const node of activeNodes) {
      if (!nodeMap.has(node.instanceId)) {
        nodeMap.set(node.instanceId, node.nodeName);
      }
    }

    const items: AdminInstanceItem[] = instances.map((instance) => {
      const approval = instance.approvalInstanceId
        ? approvalMap.get(instance.approvalInstanceId)
        : undefined;
      const submitter = this.resolveSubmitter(instance.submitter, instance.creator);
      const approvalRequired = this.isApprovalRequired(instance.definition);
      const status = approval?.status
        || instance.approvalStatus
        || instance.status;
      const submittedAt = instance.submittedAt || instance.createdAt;

      return {
        instanceId: approval?.id,
        businessInstanceId: instance.id,
        businessType: FORM_BUSINESS_TYPE,
        formKey: instance.definition?.key || instance.formKey,
        formName: instance.definition?.name,
        submitter,
        status,
        currentNode: approval?.id ? nodeMap.get(approval.id) : undefined,
        submittedAt: submittedAt.toISOString(),
        durationMs: approval?.endTime && approval?.startTime
          ? approval.endTime.getTime() - approval.startTime.getTime()
          : undefined,
        approvalRequired,
      };
    });

    const totalPages = total > 0 ? Math.ceil(total / limit) : 0;

    return {
      items,
      total,
      page,
      limit,
      totalPages,
      hasNext: page < totalPages,
      hasPrev: page > 1,
    };
  }

  async getAdminInstancesForExport(query: AdminAnalyticsQueryDto): Promise<AdminInstanceItem[]> {
    if (query.businessType && query.businessType !== FORM_BUSINESS_TYPE) {
      return [];
    }

    const where = await this.buildFormInstanceWhere(query);

    const instances = await this.prisma.formInstance.findMany({
      where,
      orderBy: [{ submittedAt: 'desc' }, { createdAt: 'desc' }],
      include: {
        definition: {
          select: { key: true, name: true, requiresApproval: true, approvalProcessKey: true },
        },
        submitter: {
          select: { id: true, displayName: true, avatar: true, email: true },
        },
        creator: {
          select: { id: true, displayName: true, avatar: true, email: true },
        },
      },
    });

    const approvalInstanceIds = instances
      .map((item) => item.approvalInstanceId)
      .filter((id): id is string => Boolean(id));

    const [approvalInstances, activeNodes] = await Promise.all([
      approvalInstanceIds.length
        ? this.prisma.approvalInstance.findMany({
            where: { id: { in: approvalInstanceIds } },
            select: {
              id: true,
              status: true,
              currentNodeId: true,
              startTime: true,
              endTime: true,
            },
          })
        : [],
      approvalInstanceIds.length
        ? this.prisma.approvalNodeInstance.findMany({
            where: {
              instanceId: { in: approvalInstanceIds },
              status: { in: ['ACTIVE', 'PENDING'] },
            },
            select: {
              instanceId: true,
              nodeName: true,
              startTime: true,
            },
            orderBy: { startTime: 'desc' },
          })
        : [],
    ]);

    const approvalMap = new Map(approvalInstances.map((item) => [item.id, item]));
    const nodeMap = new Map<string, string>();
    for (const node of activeNodes) {
      if (!nodeMap.has(node.instanceId)) {
        nodeMap.set(node.instanceId, node.nodeName);
      }
    }

    return instances.map((instance) => {
      const approval = instance.approvalInstanceId
        ? approvalMap.get(instance.approvalInstanceId)
        : undefined;
      const submitter = this.resolveSubmitter(instance.submitter, instance.creator);
      const approvalRequired = this.isApprovalRequired(instance.definition);
      const status = approval?.status
        || instance.approvalStatus
        || instance.status;
      const submittedAt = instance.submittedAt || instance.createdAt;

      return {
        instanceId: approval?.id,
        businessInstanceId: instance.id,
        businessType: FORM_BUSINESS_TYPE,
        formKey: instance.definition?.key || instance.formKey,
        formName: instance.definition?.name,
        submitter,
        status,
        currentNode: approval?.id ? nodeMap.get(approval.id) : undefined,
        submittedAt: submittedAt.toISOString(),
        durationMs: approval?.endTime && approval?.startTime
          ? approval.endTime.getTime() - approval.startTime.getTime()
          : undefined,
        approvalRequired,
      };
    });
  }

  private async buildFormInstanceWhere(
    query: AdminAnalyticsQueryDto,
  ): Promise<Prisma.FormInstanceWhereInput> {
    const andConditions: Prisma.FormInstanceWhereInput[] = [
      { deletedAt: null },
      { status: { not: 'DRAFT' } },
    ];

    if (query.formKey) {
      andConditions.push({ formKey: query.formKey });
    }

    if (query.formDefinitionId) {
      andConditions.push({ formDefinitionId: query.formDefinitionId });
    }

    if (query.approvalRequired !== undefined) {
      if (query.approvalRequired) {
        andConditions.push({
          definition: {
            requiresApproval: true,
          },
        });
      } else {
        andConditions.push({
          definition: { requiresApproval: false },
        });
      }
    }

    if (query.keyword) {
      andConditions.push({
        OR: [
          { businessKey: { contains: query.keyword, mode: 'insensitive' } },
          { formKey: { contains: query.keyword, mode: 'insensitive' } },
          { definition: { name: { contains: query.keyword, mode: 'insensitive' } } },
        ],
      });
    }

    if (query.status) {
      if (APPROVAL_STATUSES.has(query.status)) {
        andConditions.push({ approvalStatus: query.status });
      } else if (FORM_STATUSES.has(query.status)) {
        andConditions.push({ status: query.status as FormInstanceStatus });
      }
    }

    if (query.timeFrom || query.timeTo) {
      const submittedAt: Prisma.DateTimeFilter = {};
      const createdAt: Prisma.DateTimeFilter = {};

      if (query.timeFrom) {
        submittedAt.gte = new Date(query.timeFrom);
        createdAt.gte = new Date(query.timeFrom);
      }
      if (query.timeTo) {
        submittedAt.lte = new Date(query.timeTo);
        createdAt.lte = new Date(query.timeTo);
      }

      andConditions.push({
        OR: [
          { submittedAt },
          { submittedAt: null, createdAt },
        ],
      });
    }

    if (query.submitterId) {
      andConditions.push({
        OR: [
          { submittedBy: query.submitterId },
          { createdBy: query.submitterId },
        ],
      });
    }

    const submitterIds = await this.resolveSubmitterIds(query);
    if (submitterIds) {
      if (submitterIds.length === 0) {
        return { id: 'no-data' } as Prisma.FormInstanceWhereInput;
      }
      andConditions.push({
        OR: [
          { submittedBy: { in: submitterIds } },
          { createdBy: { in: submitterIds } },
        ],
      });
    }

    return { AND: andConditions };
  }

  private async resolveSubmitterIds(query: AdminAnalyticsQueryDto): Promise<string[] | undefined> {
    if (!query.organizationId && !query.departmentId) {
      return undefined;
    }

    const memberships = await this.prisma.userDepartment.findMany({
      where: {
        leftAt: null,
        ...(query.organizationId ? { organizationId: query.organizationId } : {}),
        ...(query.departmentId ? { departmentId: query.departmentId } : {}),
      },
      select: { userId: true },
      distinct: ['userId'],
    });

    return memberships.map((item) => item.userId);
  }

  private isApprovalRequired(
    definition?: { requiresApproval: boolean; approvalProcessKey?: string | null },
  ): boolean {
    return Boolean(definition?.requiresApproval);
  }

  private resolveSubmitter(
    submitter?: { id: string; displayName: string | null; avatar: string | null; email: string | null } | null,
    creator?: { id: string; displayName: string | null; avatar: string | null; email: string | null } | null,
  ): UserInfo {
    const fallback = creator || submitter;
    const current = submitter || creator || { id: '', displayName: null, avatar: null, email: null };

    return {
      id: current.id,
      name: current.displayName || fallback?.displayName || current.email || fallback?.email || '',
      avatar: current.avatar || undefined,
    };
  }

  private resolveUnifiedStatus(
    approvalInstanceId: string | null,
    approvalStatus: string | null,
    formStatus: string,
  ): string {
    if (approvalInstanceId) {
      return approvalStatus || 'RUNNING';
    }
    return formStatus;
  }

  private isApproved(status: string): boolean {
    return status === 'APPROVED' || status === 'SUBMITTED';
  }

  private isRejected(status: string): boolean {
    return status === 'REJECTED';
  }

  private isRunning(status: string): boolean {
    return status === 'RUNNING' || status === 'SUSPENDED' || status === 'PENDING_APPROVAL';
  }

  private resolveDuration(instance: {
    approvalStartTime: Date | null;
    approvalEndTime: Date | null;
    submittedAt: Date | null;
    updatedAt: Date;
  }): number | undefined {
    if (instance.approvalStartTime && instance.approvalEndTime) {
      return instance.approvalEndTime.getTime() - instance.approvalStartTime.getTime();
    }

    if (instance.submittedAt) {
      return instance.updatedAt.getTime() - instance.submittedAt.getTime();
    }

    return undefined;
  }
}
