import { Injectable, Logger } from '@nestjs/common';
import {
  ApprovalState,
  Ticket,
  TicketValidationState,
  Tour,
} from '@prisma/client';
import { Interval } from 'date-fns';
import { PatientService } from '../feat-business-objects/entities/patient/patient.service';
import { TicketService } from '../feat-business-objects/entities/ticket/ticket.service';
import { UpdateDocumentInfoDto } from '../feat-business-objects/entities/ticket/tickets.dto';
import { TourService } from '../feat-business-objects/entities/tour/tour.service';
import {
  ValidationResult,
  validatePatient,
  validatePatientDataOnTours,
  validateTour,
} from './ticket-validation.functions';

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

  constructor(
    private readonly patientService: PatientService,
    private readonly ticketService: TicketService,
    private readonly tourService: TourService,
  ) {}

  async createTickets(interval: Interval): Promise<void> {
    try {
      const tours = await this.tourService.findInInterval(interval);

      for (const tour of tours) {
        if (tour.ticketId) {
          continue;
        }

        const ticketId = await this.searchTicketIDForTour(tour);
        if (ticketId) {
          await this.ticketService.addToursToTicket(ticketId, [tour]);
        } else {
          await this.ticketService.createTicket([tour]);
        }
      }

      await this.validateCreatedTickets();
      // await this.archiveClosedTickets();
    } catch (error) {
      this.logger.error(error);
    }
  }

  async validateDocumentInfoUpdate(
    ticketId: string,
    documentInfo: UpdateDocumentInfoDto,
  ) {
    const ticket = await this.ticketService.findById(ticketId);

    switch (ticket.currentState) {
      case TicketValidationState.USER_INTERACTION_REQUIRED:
        const approvalStateUpdate = documentInfo.approval.isNeeded
          ? ApprovalState.REQUESTED
          : ApprovalState.FREE;

        let stateUpdate: TicketValidationState =
          TicketValidationState.USER_INTERACTION_REQUIRED;
        if (
          documentInfo.tdLocation === 'company' &&
          approvalStateUpdate === ApprovalState.FREE
        )
          stateUpdate = TicketValidationState.ARCHIVED;
        else if (documentInfo.tdLocation === 'uncertain')
          stateUpdate = TicketValidationState.TD_UNCERTAIN;
        else stateUpdate = TicketValidationState.DOCUMENTS_MISSING;

        return this.ticketService.updateTicket(ticketId, {
          documentInfo,
          approvalState: approvalStateUpdate,
          state: stateUpdate,
        });

      case TicketValidationState.TD_UNCERTAIN:
        return this.ticketService.updateTicket(ticketId, {
          documentInfo,
          state:
            ticket.approvalState === ApprovalState.FREE &&
            documentInfo.tdLocation === 'company'
              ? TicketValidationState.ARCHIVED
              : TicketValidationState.DOCUMENTS_MISSING,
        });
      case TicketValidationState.DOCUMENTS_MISSING:
        return this.ticketService.updateTicket(ticketId, {
          documentInfo,
          ...((ticket.approvalState === ApprovalState.FREE ||
            documentInfo.approval.location === 'company') &&
          documentInfo.tdLocation === 'company'
            ? { state: TicketValidationState.ARCHIVED }
            : {}),
        });
    }
  }

  private async searchTicketIDForTour(tour: Tour): Promise<string | undefined> {
    const tours = await Promise.all([
      this.tourService.findFirstRecurring(tour),
      this.tourService.findOnSameDayWithOppositeDirection(tour),
    ]);
    const firstFoundTour = tours.filter((tour) => !!tour)[0];
    if (firstFoundTour) {
      const ticket = await this.ticketService.findIdByTourId(firstFoundTour.id);
      return ticket?.id;
    }
    return undefined;
  }

  private async validateCreatedTickets(): Promise<void> {
    const tickets = await this.ticketService.findAllCreated();
    this.logger.debug(`Found ${tickets.length} with state CREATED`);

    for (const ticket of tickets) {
      const {
        result: isValid,
        errors,
        notes,
      } = await this.validateCreatedTicket(ticket);

      if (isValid) {
        const { result: approvalResult } = await this.isApprovalNeeded(ticket);
        if (approvalResult) {
          await this.ticketService.updateTicket(ticket.id, {
            errors,
            notes,
            state: TicketValidationState.USER_INTERACTION_REQUIRED,
            approvalState: ticket.tours.every(({ patientId }) => !patientId)
              ? ApprovalState.UNKNOWN
              : ApprovalState.REQUIRED,
          });
        } else {
          // NOTE: Condition required to get these tickets out of USER_INTERACTION_REQUIRED
          if (
            ticket.tours.every(
              ({ ordinanceType }) =>
                ordinanceType.indexOf('BTW') > -1 ||
                ordinanceType.indexOf('TSW') > -1,
            )
          ) {
            await this.ticketService.updateTicket(ticket.id, {
              errors,
              notes,
              state: TicketValidationState.TD_UNCERTAIN,
              approvalState: ApprovalState.FREE,
            });
          } else {
            await this.ticketService.updateTicket(ticket.id, {
              errors,
              notes,
              state: TicketValidationState.USER_INTERACTION_REQUIRED,
              approvalState: ApprovalState.FREE,
            });
          }
        }
      } else {
        await this.ticketService.setActionablesOnTicket(
          ticket.id,
          errors,
          notes,
        );
      }
    }
  }

  private async archiveClosedTickets() {
    const closedTickets = await this.ticketService.findAllClosed();
    for (const closedTicket of closedTickets) {
      await this.ticketService.setStateOfTicket(
        closedTicket.id,
        TicketValidationState.ARCHIVED,
      );
    }
  }

  private async validateCreatedTicket(
    ticket: Ticket & { tours: Tour[] },
  ): Promise<ValidationResult> {
    const patientIds = [
      ...new Set(ticket.tours.map(({ patientId }) => patientId)),
    ];
    if (patientIds.length > 1) {
      return {
        result: false,
        errors: ['Ticket: Die Touren umfassen mehr als einen Patienten'],
        notes: [],
      };
    }
    const [patientId] = patientIds;
    if (!patientId && ticket.tours.at(0)?.type !== 'Spontan') {
      return {
        result: false,
        errors: [],
        notes: ['Patient: Der Patient ist nicht in DispoLive hinterlegt'],
      };
    }

    let patientResult: ValidationResult;
    if (!patientId && ticket.tours.at(0)?.type === 'Spontan') {
      patientResult = validatePatientDataOnTours(ticket.tours);
    } else {
      const patient = await this.patientService.findById(patientId);
      if (!patient) {
        patientResult = validatePatientDataOnTours(ticket.tours);
      } else {
        patientResult = validatePatient(patient);
      }
    }
    const tourResults = ticket.tours.map((tour) => validateTour(tour));

    return [patientResult, ...tourResults].reduce(
      (result, results) => ({
        result: result.result && results.result,
        errors: [...result.errors, ...results.errors],
        notes: [...result.notes, ...results.notes],
      }),
      { result: true, errors: [], notes: [] } as ValidationResult,
    );
  }

  private async isApprovalNeeded(
    ticket: Ticket & { tours: Tour[] },
  ): Promise<ValidationResult> {
    const approvalRequirements = await Promise.all(
      ticket.tours.map((tour) => this.checkTourApprovalRequirement(tour)),
    );
    const isApprovalRequired = approvalRequirements.reduce(
      (result, requirement) => result || requirement,
      false,
    );

    return {
      result: isApprovalRequired,
      errors: [],
      notes:
        isApprovalRequired && ticket.tours.every(({ patientId }) => !patientId)
          ? [
              'Patient nicht in Stammdaten: Prüfen Sie ob eine Genehmigung benötigt wird',
            ]
          : [],
    };
  }

  private async checkTourApprovalRequirement(tour: Tour): Promise<boolean> {
    // NOTE: Requirement by Avicenna
    if (
      tour.ordinanceType.indexOf('BTW') > -1 ||
      tour.ordinanceType.indexOf('TSW') > -1
    ) {
      return false;
    }

    if (
      [
        'Einweisung',
        'Entlassung',
        'Entlassung Vorbestellung',
        'Konsil KH - Dia',
        'Privatfahrt Rechnung',
        'Tagesklinik',
        'Verlegung',
      ].includes(tour.category) ||
      tour.healthInsurance === 'KOSTENTRÄGER PATIENT'
    ) {
      return false;
    }

    if (
      tour.patientId &&
      ['BTW', 'TSW'].includes(tour.ordinanceType) &&
      tour.category !== 'Dialyse'
    ) {
      const patient = await this.patientService.findById(tour.patientId);
      if (
        ['3', '4', '5'].includes(patient.careDegree) &&
        (['aG', 'Bi', 'H'].includes(patient.disabilityMark1) ||
          ['aG', 'Bi', 'H'].includes(patient.disabilityMark2))
      ) {
        return false;
      }
    }

    return true;
  }
}
