import {
  Args,
  Mutation,
  Parent,
  Query,
  ResolveField,
  Resolver,
} from '@nestjs/graphql';
import { Anomaly } from '@prisma/client';
import { PaginationArgs } from 'src/core/base/pagination.input';
import {
  decodeOffsetCursor,
  encodeCursor,
} from 'src/core/base/pagination.util';
import { TourObjectType } from '../app-accounting/tour.object-type';
import { AnomalyTypes } from '../feat-business-objects/entities/anomaly/anomaly-types.enum';
import { AnomalyService } from '../feat-business-objects/entities/anomaly/anomaly.service';
import { KpiService } from '../feat-kpis/kpi.service';
import {
  AnomalyObjectType,
  AssociatedEntitiesObjectType,
  PaginatedAnomalyGroupObjectType,
  PaginatedAnomalyObjectType,
  SolutionTargetObjectType,
} from './anomaly.object-type';

@Resolver(() => AnomalyObjectType)
export class ControlCenterResolver {
  constructor(
    private readonly kpiService: KpiService,
    private readonly anomalyService: AnomalyService,
  ) {}

  @Query(() => [TourObjectType])
  async upcomingTours() {
    return this.kpiService.upcomingTours();
  }

  @Query(() => AnomalyObjectType)
  async anomaly(@Args('id') id: string) {
    return this.anomalyService.findById(id);
  }

  @Query(() => [AnomalyObjectType])
  async anomaliesProto() {
    return this.anomalyService.findAllVisible();
  }

  @Query(() => PaginatedAnomalyObjectType)
  async anomalies(@Args() { cursor, take }: PaginationArgs) {
    const skip = cursor ? decodeOffsetCursor(cursor) : 0;
    const activeAnomalies = await this.anomalyService.findAllVisible(
      skip,
      take,
      [AnomalyTypes.WorkingEmployeeNotInDistributionSystem],
    );

    const totalCount = await this.anomalyService.countVisibleAnomalies([
      AnomalyTypes.WorkingEmployeeNotInDistributionSystem,
    ]);

    const prevPage = skip - take < 0 ? 0 : skip - take;
    const nextPage = skip + take < totalCount ? skip + take : skip;

    return {
      nodes: activeAnomalies,
      prevCursor: encodeCursor(prevPage),
      endCursor: encodeCursor(nextPage),
      hasNextPage: nextPage !== skip,
      totalCount,
    };
  }

  @Query(() => PaginatedAnomalyGroupObjectType)
  async groupedAnomalies(@Args() { cursor, take }: PaginationArgs) {
    const skip = cursor ? decodeOffsetCursor(cursor) : 0;

    const { totalCount, anomalies: groupedActiveAnomalies } =
      await this.findAnomaliesVisibleGroupedByGroupKey(skip, take);

    const prevPage = skip - take < 0 ? 0 : skip - take;
    const nextPage = skip + take < totalCount ? skip + take : skip;

    return {
      nodes: groupedActiveAnomalies,
      prevCursor: encodeCursor(prevPage),
      endCursor: encodeCursor(nextPage),
      hasNextPage: nextPage !== skip,
      totalCount,
    };
  }

  @Mutation(() => AnomalyObjectType)
  async setAnomalyToSleep(@Args({ name: 'id' }) anomalyId: string) {
    return this.anomalyService.snoozeAnomaly(anomalyId);
  }

  @Mutation(() => AnomalyObjectType)
  async acceptAnomaly(@Args({ name: 'id' }) anomalyId: string) {
    return this.anomalyService.acceptAnomaly(anomalyId);
  }

  @Query(() => AssociatedEntitiesObjectType)
  async associatedEntities(@Args({ name: 'id' }) anomalyId: string) {
    return this.anomalyService.getAssociatedEntities(anomalyId);
  }

  //! Used when creating anomaly list
  // @ResolveField(() => TourObjectType, { nullable: true })
  // tour(@Parent() anomaly: AnomalyObjectType) {
  //   const [operationId] = anomaly.groupKey.split('-').reverse();
  //   return this.anomaliesService.findTourByOperationId(operationId);
  // }

  @ResolveField(() => [SolutionTargetObjectType])
  solution(
    @Parent() anomaly: AnomalyObjectType,
  ): Promise<SolutionTargetObjectType[]> {
    return this.anomalyService.findSolutionTargetsByAnomalyId(anomaly.id);
  }

  private async findAnomaliesVisibleGroupedByGroupKey(
    skip = 0,
    take = 10,
  ): Promise<{
    totalCount: number;
    anomalies: { key: string; anomalies: Anomaly[] }[];
  }> {
    const activeAnomalies = await this.anomalyService.findAllVisible(
      //! 2025-06-11: This is intended, we reduce the response at the end
      undefined,
      undefined,
      [
        AnomalyTypes.DriverCertificate,
        AnomalyTypes.EmployeesLoggedIn,
        AnomalyTypes.OneTourAtATime,
      ],
    );

    const groupedAnomalies = this.groupAnomaliesByGroupKey(activeAnomalies);

    return {
      totalCount: Object.keys(groupedAnomalies).length,
      anomalies: Object.entries(groupedAnomalies)
        .map(([key, anomalies]) => ({
          key,
          anomalies,
        }))
        .slice(skip, skip + take),
    };
  }

  private groupAnomaliesByGroupKey(
    anomalies: Anomaly[],
  ): Record<string, Anomaly[]> {
    return anomalies.reduce(
      (grouped, anomaly) => ({
        ...grouped,
        [anomaly.groupKey]: [...(grouped[anomaly.groupKey] ?? []), anomaly],
      }),
      {} as Record<string, Anomaly[]>,
    );
  }
}
