import { Injectable } from '@nestjs/common';
import { CreateInoutDto } from './dto/create-inout.dto';
import { UpdateInoutDto } from './dto/update-inout.dto';
import { PrismaService } from '../prisma/prisma.service';
import * as MomentJ from 'moment-jalaali';
import * as moment from 'moment';
import { Inout_logs } from './inout_logs';

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

  create(createInoutDto: CreateInoutDto, user_id) {
    return this.prisma.inout
      .create({
        data: {
          time: createInoutDto.time,
          user_id: user_id,
          type: createInoutDto.type,
          description: createInoutDto.description,
        },
      })
      .then(async (e) => {
        await new Inout_logs(
          e.id,
          'add',
          'زمان به صورت دستی افزوده شد!',
          e.time,
        ).addLog();
        return e;
      });
  }

  async findAll(id: string, year: string, month: string, manager: string) {
    const jalali = MomentJ(`${year}/${month}/1`, 'jYYYY-jM-jD');
    const start = jalali.format('YYYY-MM-DD H:mm:ss');
    jalali.jMonth(parseInt(month) - 1);
    const end = jalali.endOf('jMonth').format('YYYY-MM-DD H:mm:ss');
    let HowManyDays = 0;
    if (parseInt(month) > 6) {
      if (parseInt(month) != 12) {
        HowManyDays = 30;
      } else {
        if (MomentJ.jIsLeapYear(year)) {
          HowManyDays = 30;
        } else {
          HowManyDays = 29;
        }
      }
    } else {
      HowManyDays = 31;
    }
    let Holidays = [];
    Holidays = await this.prisma.holiday.findMany({
      where: {
        date: {
          gte: new Date(start).toISOString(),
          lte: new Date(end).toISOString(),
        },
      },
    });

    let Vacations = [];
    Vacations = await this.prisma.vacation.findMany({
      where: {
        from: {
          gte: new Date(start).toISOString(),
          lte: new Date(end).toISOString(),
        },
        to: {
          gte: new Date(start).toISOString(),
          lte: new Date(end).toISOString(),
        },
        user_id: id,
        status: 'accepted',
      },
      select: {
        id: true,
        from: true,
        to: true,
        type: true,
      },
    });

    const ItemsWHERE = {
      user_id: id,
      delete: false,
      time: {
        gte: new Date(start).toISOString(),
        lte: new Date(end).toISOString(),
      },
    };

    const Items = await this.prisma.inout.findMany({
      where:
        manager === 'true'
          ? { ...ItemsWHERE }
          : { ...ItemsWHERE, approved: true },
      select: { time: true, id: true, type: true, approved: true },
      orderBy: { time: 'asc' },
    });
    const user = await this.prisma.user.findUnique({
      where: { id },
      select: {
        userDetail: {
          select: {
            delay_time: true,
            delay_ratio: true,
          },
        },
      },
    });

    const grouped_day = [];
    let time_passed = 0;
    let vacation_minutes = 0;
    let delay_minutes = 0;
    let in_company = 0;
    //add days
    Array.apply(0, Array(HowManyDays)).map((el, i) => {
      grouped_day.push({
        date: `${year}-${parseInt(month) <= 9 ? '0' + month : month}-${
          i + 1 <= 9 ? '0' + (i + 1).toString() : i + 1
        }`,
        detail: [],
        holiday: false,
        vacation: false,
        attendance_time: 0,
        pure_time: 0,
        vacation_time: 0,
        delay_time: 0,
        total_time: 0,
      });
    });

    //add holidays
    for (const groupedDayElement of Holidays) {
      const objIndex = grouped_day.findIndex(
        (x) =>
          x.date === MomentJ(groupedDayElement.date).format('jYYYY-jMM-jDD'),
      );
      grouped_day[objIndex].holiday = true;
    }

    //add items
    for (const item of Items) {
      const shamsi_day = MomentJ(item.time).format('jYYYY-jMM-jDD');
      const before = grouped_day.find((x) => x.date === shamsi_day);
      if (before) {
        before.detail.push(item);
      } else {
        grouped_day.push({
          date: shamsi_day,
          detail: [item],
          holiday: false,
        });
      }
    }

    //add vacations
    for (const vac of Vacations) {
      if (vac.type === 'daily') {
        const duration = MomentJ.duration(MomentJ(vac.to).diff(vac.from));
        const days_duration = Math.floor(duration.asDays());
        const started_date_index = grouped_day.findIndex(
          (x) => x.date === MomentJ(vac.from).format('jYYYY-jMM-jDD'),
        );

        if (days_duration === 0) {
          if (!grouped_day[started_date_index].holiday) {
            grouped_day[started_date_index].vacation = true;
            grouped_day[started_date_index].vacation_time = 480;
            grouped_day[started_date_index].total_time = 480;
          }
        } else {
          for (let i = 0; i < days_duration; i++) {
            if (!grouped_day[started_date_index + i].holiday) {
              grouped_day[started_date_index + i].vacation = true;
              grouped_day[started_date_index + i].vacation_time = 480;
              grouped_day[started_date_index + i].total_time = 480;
              vacation_minutes += 480;
            }
          }
        }
      } else {
        const objIndex = grouped_day.findIndex(
          (x) => x.date === MomentJ(vac.from).format('jYYYY-jMM-jDD'),
        );
        grouped_day[objIndex].detail.push({
          time: vac.from,
          id: vac.id,
          type: 'vacation',
          approved: vac.status === 'accepted',
        });
        grouped_day[objIndex].detail.push({
          time: vac.to,
          id: vac.id,
          type: 'vacation',
          approved: vac.status === 'accepted',
        });
        const duration = MomentJ.duration(MomentJ(vac.to).diff(vac.from));
        const minutes_duration = Math.floor(duration.asMinutes());
        grouped_day[objIndex].vacation_time += minutes_duration;
        vacation_minutes += minutes_duration;
      }
    }

    //sort items
    for (const groupedDayElement of grouped_day) {
      if (groupedDayElement.detail.length > 0) {
        groupedDayElement.detail.sort((a, b) => (a.time > b.time ? 1 : -1));
      }
    }

    const getDuration = (first, second) => {
      const start = moment(first);
      const end = moment(second);
      const duration = end.diff(start, 'minutes');
      return duration;
    };

    //set times
    for (const groupedDayElement of grouped_day) {
      const item_length = groupedDayElement.detail.length;
      if (item_length > 0) {
        const findFirstFinger = groupedDayElement.detail[0];
        const firstTime = MomentJ(findFirstFinger.time).set({
          hour: user.userDetail.delay_time.slice(0, 2),
          minute: user.userDetail.delay_time.slice(3, 5),
        });
        const duration = MomentJ.duration(
          MomentJ(findFirstFinger.time).local().diff(firstTime),
        );
        if (duration > 0) {
          const minutes_duration = Math.floor(duration.asMinutes());
          // console.log(minutes_duration);
          delay_minutes += minutes_duration;
        }
      }
      if (item_length > 1) {
        const attendance = getDuration(
          groupedDayElement.detail[0].time,
          groupedDayElement.detail[item_length - 1].time,
        );
        groupedDayElement.attendance = attendance;
        let pure_time = 0;
        for (let i = 0; i < item_length; i += 2) {
          const twoes = groupedDayElement.detail.slice(i, i + 2);
          pure_time += getDuration(
            twoes[0].time,
            twoes.length == 2 ? twoes[1].time : twoes[0].time,
          );
        }
        if (groupedDayElement.type !== 'vacation') {
          groupedDayElement.attendance_time = pure_time;
          in_company += pure_time;
          groupedDayElement.pure_time = pure_time;
          const rest_time = (pure_time / 53) * 7;
          if (attendance - pure_time > rest_time) {
            groupedDayElement.total_time = pure_time + rest_time;
            time_passed += pure_time + rest_time;
          } else {
            groupedDayElement.total_time = attendance;
            // time_passed += attendance + groupedDayElement.vacation_time;
            time_passed += attendance;
          }
        }
      } else {
        // if (groupedDayElement.vacation) {
        //   time_passed += 480;
        // }
      }
    }

    //sort days
    const sortedDays = grouped_day.sort((a, b) => (a.date > b.date ? 1 : -1));

    const calcWorkDays = HowManyDays - Holidays.length;
    const calcWorkMinutes = calcWorkDays * 7.33 * 60;
    return {
      items: sortedDays,
      days: HowManyDays,
      work_days: calcWorkDays,
      work_minutes: calcWorkMinutes,
      work_passed: time_passed,
      in_company: in_company,
      vacation_minutes: vacation_minutes,
      delay_minutes: delay_minutes,
    };
  }

  async findOne(id: string) {
    return this.prisma.inout.findUnique({
      where: { id },
      select: {
        id: true,
        time: true,
        description: true,
        delete: true,
        type: true,
        approved: true,
        Inout_logs: {
          select: {
            action: true,
            old_time: true,
            new_time: true,
            description: true,
            createdAt: true,
          },
          orderBy: {
            createdAt: 'asc',
          },
        },
      },
    });
  }

  async update(id: string, updateInoutDto: UpdateInoutDto) {
    const inout = await this.prisma.inout.findUnique({
      where: { id },
      select: {
        type: true,
        user_id: true,
        time: true,
      },
    });
    if (updateInoutDto.manager) {
      return this.prisma.inout
        .update({
          where: { id },
          data: {
            approved: updateInoutDto.approved === 'true',
          },
        })
        .then(async (e) => {
          await new Inout_logs(
            e.id,
            'edit',
            updateInoutDto.approved === 'true'
              ? 'زمان شما توسط مدیر تایید شد.'
              : 'متاسفانه زمان شما توسط مدیر تایید نشد.',
            inout.time,
          ).addLog();
          return e;
        });
    } else {
      if (
        inout.type === 'custom' ||
        new Date(inout.time).getTime() ===
          new Date(updateInoutDto.time).getTime()
      ) {
        return this.prisma.inout
          .update({
            where: { id },
            data: {
              time: updateInoutDto.time,
              description: updateInoutDto.description,
            },
          })
          .then(async (e) => {
            const isDescriptionEdit =
              new Date(inout.time).getTime() ===
              new Date(updateInoutDto.time).getTime()
                ? true
                : false;
            await new Inout_logs(
              e.id,
              'edit',
              isDescriptionEdit ? 'توضیحات ویرایش شد.' : 'زمان ویرایش شد.',
              null,
            ).addLog();
            return e;
          });
      } else {
        const new_item = this.prisma.inout
          .create({
            data: {
              time: updateInoutDto.time,
              user_id: inout.user_id,
              type: 'custom',
              description: updateInoutDto.description,
            },
          })
          .then(async (e) => {
            await new Inout_logs(
              e.id,
              'edit',
              `زمان توسط کاربر ویرایش شد. شناسه قبلی: ${id}`,
              updateInoutDto.time,
            ).addLog();
            return e;
          });
        await this.prisma.inout
          .update({
            where: { id },
            data: {
              delete: true,
            },
          })
          .then(async (e) => {
            await new Inout_logs(
              e.id,
              'delete',
              `به دلیل ویرایش این زمان حذف و به آیتم دیگری تغییر یافت. شناسه جدید: ${
                (
                  await new_item
                ).id
              }`,
              updateInoutDto.time,
            ).addLog();
            return e;
          });
        return new_item;
      }
    }
  }

  remove(id: string) {
    return this.prisma.inout
      .update({
        where: { id },
        data: {
          delete: true,
        },
      })
      .then(async (e) => {
        await new Inout_logs(e.id, 'delete', 'زمان حذف شد', null).addLog();
        return e;
      });
  }

  async users(user_id) {
    const this_user = await this.prisma.user.findUnique({
      where: { id: user_id },
      include: {
        userDetail: true,
      },
    });
    return this.prisma.user.findMany({
      where: this_user.userDetail.manager_id
        ? {
            userDetail: {
              manager_id: user_id,
            },
          }
        : {},
      select: {
        id: true,
        userDetail: {
          select: {
            first_name: true,
            last_name: true,
          },
        },
      },
    });
  }
}
