import { HttpService } from '@nestjs/axios';
import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import {
  Interval,
  eachDayOfInterval,
  eachMonthOfInterval,
  format,
  getMonth,
  getYear,
} from 'date-fns';
import {
  EMPTY,
  Observable,
  catchError,
  concatMap,
  expand,
  from,
  map,
  of,
  reduce,
  switchMap,
} from 'rxjs';
import {
  AttendanceRegistrationRaw,
  EmployeeDyflexisRaw,
  PlannedTimeRaw,
  WorkTimeRaw,
} from './source.interface';
import { SourceRepository } from './source.repository';
import { ConfigService } from 'src/core/config/config.service';

@Injectable()
export class DyflexisExtractorsService {
  constructor(
    private readonly http: HttpService,
    private readonly repository: SourceRepository,
    private readonly config: ConfigService,
  ) {}

  extractEmployees(jobId: string): Observable<Prisma.BatchPayload> {
    return this.fetchEmployees().pipe(
      switchMap((rawEmployees) =>
        from(this.repository.createEmployeesDyflexis(rawEmployees, jobId)),
      ),
    );
  }

  fetchEmployees(): Observable<EmployeeDyflexisRaw[]> {
    const endpoint = '/employee';

    const getEmployeesPage = (pageNumber: number) =>
      this.http
        .get<{ response: { data: EmployeeDyflexisRaw[] } }>(
          `${this.config.dyflexisV0BaseUrl()}${endpoint}?page=${pageNumber}`,
          {
            headers: {
              'Content-Type': 'application/json',
              'X-Dyflexis-AuthToken': `${this.config.dyflexisV0ApiKey()}`,
            },
          },
        )
        .pipe(
          map((response) =>
            response.data.response.data.map((employee) => {
              const {
                id,
                firstname,
                surname,
                contract_id,
                contract_start,
                contract_end,
                contract_hours_week,
                contract_salary_hour,
              } = employee;
              return {
                id,
                firstname,
                surname,
                contract_id,
                contract_start,
                contract_end,
                contract_hours_week,
                contract_salary_hour,
              };
            }),
          ),
        );

    return new Observable<EmployeeDyflexisRaw[]>((observer) =>
      getEmployeesPage(0)
        .pipe(
          expand((data, i) => (data.length ? getEmployeesPage(i + 1) : EMPTY)),
          reduce((acc, data) => [...acc, ...data], []),
          catchError((error) => {
            observer.error(error);
            return of(error);
          }),
        )
        .subscribe((employees) => {
          observer.next(employees);
          observer.complete();
        }),
    );
  }

  extractAttendanceRegistrations(
    interval: Interval,
    jobId: string,
  ): Observable<Prisma.BatchPayload> {
    const daysInInterval = eachDayOfInterval(interval);
    return from(daysInInterval).pipe(
      concatMap((day) => this.fetchAttendanceRegistrationsForDay(day)),
      reduce(
        (full, attendanceRegistrations) => [
          ...full,
          ...attendanceRegistrations,
        ],
        [],
      ),
      switchMap((rawAttendanceRegistrations) =>
        from(
          this.repository.createAttendanceRegistrations(
            rawAttendanceRegistrations,
            jobId,
          ),
        ),
      ),
    );
  }

  fetchAttendanceRegistrationsForDay(
    date: Date | string,
  ): Observable<AttendanceRegistrationRaw[]> {
    const endpoint = '/attendance-registration';

    return this.http
      .get<{ attendanceRegistration: AttendanceRegistrationRaw[] }>(
        `${this.config.dyflexisV2BaseUrl()}${endpoint}/${format(
          new Date(date),
          'yyyy-MM-dd',
        )}`,
        {
          headers: {
            'Content-Type': 'application/json',
            Authorization: `Token ${this.config.dyflexisV2ApiKey()}`,
          },
        },
      )
      .pipe(
        map((response) =>
          response.data.attendanceRegistration.map((attendanceRegistration) => {
            const {
              attendanceRegistrationId,
              employeeId,
              personnelNumber,
              dateTime,
              event,
            } = attendanceRegistration;
            return {
              attendanceRegistrationId: attendanceRegistrationId
                ? `${attendanceRegistrationId}`
                : undefined,
              employeeId: employeeId ? `${employeeId}` : undefined,
              personnelNumber: personnelNumber
                ? `${personnelNumber}`
                : undefined,
              dateTime: dateTime ? `${dateTime}` : undefined,
              event: event ? `${event}` : undefined,
            };
          }),
        ),
      );
  }

  extractWorkTimes(
    interval: Interval,
    jobId: string,
  ): Observable<Prisma.BatchPayload> {
    const monthsInInterval = eachMonthOfInterval(interval);

    return from(monthsInInterval).pipe(
      concatMap((month) =>
        this.fetchWorkTimeForMonth(getMonth(month) + 1, getYear(month)),
      ),
      reduce((full, workTimes) => [...full, ...workTimes], []),
      switchMap((rawWorkTimes) =>
        from(this.repository.createWorkTimes(rawWorkTimes, jobId)),
      ),
    );
  }

  private fetchWorkTimeForMonth(
    month: number,
    year: number,
  ): Observable<WorkTimeRaw[]> {
    const endpoint = '/worked';

    const getWorkTimePage = (pageNumber: number) =>
      this.http
        .get<{ response: { data: WorkTimeRaw[] } }>(
          `${this.config.dyflexisV0BaseUrl()}${endpoint}?month=${month}&year=${year}&page=${pageNumber}`,
          {
            headers: {
              'Content-Type': 'application/json',
              'X-Dyflexis-AuthToken': `${this.config.dyflexisV0ApiKey()}`,
            },
          },
        )
        .pipe(
          map((response) =>
            response.data.response.data.map((workTime) => {
              const {
                id,
                user_id,
                firstname,
                surname,
                department_id,
                department_name,
                start_date,
                end_date,
                pauze,
                duration,
              } = workTime;
              return {
                id,
                user_id,
                firstname,
                surname,
                department_id,
                department_name,
                start_date,
                end_date,
                pauze,
                duration,
              };
            }),
          ),
        );
    return new Observable<WorkTimeRaw[]>((observer) =>
      getWorkTimePage(0)
        .pipe(
          expand((data, i) => (data.length ? getWorkTimePage(i + 1) : EMPTY)),
          reduce((acc, data) => [...acc, ...data], []),
          catchError((error) => {
            observer.error(error);
            return of(error);
          }),
        )
        .subscribe((workTimes) => {
          observer.next(workTimes);
          observer.complete();
        }),
    );
  }

  extractPlannedTimes(
    interval: Interval,
    jobId: string,
  ): Observable<Prisma.BatchPayload> {
    const monthsInInterval = eachMonthOfInterval(interval);

    return from(monthsInInterval).pipe(
      concatMap((month) =>
        this.fetchPlannedTimeForMonth(getMonth(month) + 1, getYear(month)),
      ),
      reduce((full, plannedTimes) => [...full, ...plannedTimes], []),
      switchMap((rawPlannedTimes) =>
        from(this.repository.createPlannedTimes(rawPlannedTimes, jobId)),
      ),
    );
  }

  fetchPlannedTimeForMonth(
    month: number,
    year: number,
  ): Observable<PlannedTimeRaw[]> {
    const endpoint = '/planned';

    const getPlannedTimePage = (pageNumber: number) =>
      this.http
        .get<{ response: { data: PlannedTimeRaw[] } }>(
          `${this.config.dyflexisV0BaseUrl()}${endpoint}?month=${month}&year=${year}&page=${pageNumber}`,
          {
            headers: {
              'Content-Type': 'application/json',
              'X-Dyflexis-AuthToken': `${this.config.dyflexisV0ApiKey()}`,
            },
          },
        )
        .pipe(
          map((response) =>
            response.data.response.data.map((plannedTime) => {
              const {
                id,
                user_id,
                firstname,
                surname,
                department_id,
                department_name,
                start_date,
                end_date,
                pauze,
                duration,
                deleted,
                mark,
              } = plannedTime;
              return {
                id,
                user_id,
                firstname,
                surname,
                department_id,
                department_name,
                start_date,
                end_date,
                pauze,
                duration,
                deleted,
                mark,
              };
            }),
          ),
        );
    return new Observable<PlannedTimeRaw[]>((observer) =>
      getPlannedTimePage(0)
        .pipe(
          expand((data, i) =>
            data.length ? getPlannedTimePage(i + 1) : EMPTY,
          ),
          reduce((acc, data) => [...acc, ...data], []),
          catchError((error) => {
            observer.error(error);
            return of(error);
          }),
        )
        .subscribe((plannedTimes) => {
          observer.next(plannedTimes);
          observer.complete();
        }),
    );
  }
}
