import { Injectable } from '@nestjs/common';
import {
  Anomaly,
  Prisma,
  SolutionTarget,
  SolutionTargetStep,
} from '@prisma/client';
import { subMilliseconds } from 'date-fns';
import { PrismaService } from 'src/core/database/prisma.service';
import { AnomalyCreateDto } from './anomalies.dto';
import { ANOMALY_SLEEP_TIME_MS } from './anomalies.token';
import { AnomalyTypes } from './anomaly-types.enum';

@Injectable()
export class AnomalyRepository {
  constructor(private readonly prisma: PrismaService) {}

  // Create anomaly if ID is not found, otherwise continue without change
  async createEvenIfExists(anomaly: AnomalyCreateDto): Promise<Anomaly> {
    return await this.prisma.anomaly.upsert({
      where: {
        id: anomaly.id,
      },
      create: {
        ...anomaly,
      },
      update: {},
    });
  }

  // Resolves the anomaly
  async resolveAnomaly(anomalyId: string): Promise<Anomaly> {
    return await this.prisma.anomaly.update({
      where: {
        id: anomalyId,
      },
      data: {
        resolvedAt: new Date(),
        manuallyResolved: false,
      },
    });
  }

  async snoozeAnomaly(anomalyId: string): Promise<Anomaly> {
    return await this.prisma.anomaly.update({
      where: {
        id: anomalyId,
      },
      data: {
        sleepSince: new Date(),
      },
    });
  }

  async acceptAnomaly(anomalyId: string): Promise<Anomaly> {
    return await this.prisma.anomaly.update({
      where: {
        id: anomalyId,
      },
      data: {
        resolvedAt: new Date(),
        manuallyResolved: true,
      },
    });
  }

  // Find all anomalies which are unresolved
  async findAllUnsolved(): Promise<Anomaly[]> {
    return await this.prisma.anomaly.findMany({
      where: {
        resolvedAt: null,
      },
    });
  }

  async findAllVisible(
    skip?: number,
    take?: number,
    types: AnomalyTypes[] = [],
  ): Promise<Anomaly[]> {
    return await this.prisma.anomaly.findMany({
      ...{
        ...(skip ? { skip, take: 10 } : {}),
        ...(take ? { take } : {}),
        ...(take && !skip ? { skip: 0 } : {}),
      },
      where: this.visibleAnomalyQuery(types),
      orderBy: {
        groupKey: 'asc',
      },
      include: {
        solution: true,
      },
    });
  }

  async findById(anomalyId: string): Promise<Anomaly> {
    return await this.prisma.anomaly.findUnique({
      where: {
        id: anomalyId,
      },
    });
  }

  async countVisibleAnomalies(types: AnomalyTypes[] = []): Promise<number> {
    return await this.prisma.anomaly.count({
      where: {
        ...this.visibleAnomalyQuery(types),
      },
    });
  }

  async findSolutionTargetsByAnomalyId(
    anomalyId: string,
  ): Promise<SolutionTarget[]> {
    return await this.prisma.solutionTarget.findMany({
      where: {
        anomalyId,
      },
    });
  }

  async findSolutionTargetStepsBySolutionTargetId(
    solutionTargetId: string,
  ): Promise<SolutionTargetStep[]> {
    return await this.prisma.solutionTargetStep.findMany({
      where: {
        solutionTargetId,
      },
    });
  }

  // Filter by `resolvedAt` and `sleepSince`
  private visibleAnomalyQuery(types: AnomalyTypes[]): Prisma.AnomalyWhereInput {
    return {
      ...(types.length ? { type: { in: types } } : {}),
      resolvedAt: null,
      OR: [
        {
          sleepSince: null,
        },
        {
          sleepSince: {
            lt: subMilliseconds(new Date(), ANOMALY_SLEEP_TIME_MS),
          },
        },
      ],
    };
  }
}
