/* ============================================================
   EATME PORTÁL – ATTENDANCE CORRECTION ADMIN SERVICE

   Admin / manager:
   - seznam čekajících žádostí o opravu docházky
   - schválení -> přepis SHIFTS + přepočet minut
   - zamítnutí -> pouze změna stavu žádosti
   - uzamčenou směnu nelze měnit
============================================================ */


/* ============================================================
   SEZNAM ŽÁDOSTÍ
============================================================ */

function getPendingAttendanceCorrections(token) {

  requireUser_(
    token,
    [
      'ADMIN',
      'MANAGER'
    ]
  );


  const requests =
    displayRows_(
      CFG.SHEETS.SHIFT_CHANGE_REQUESTS
    );


  const shifts =
    displayRows_(
      CFG.SHEETS.SHIFTS
    );


  const employees =
    displayRows_(
      CFG.SHEETS.EMPLOYEES
    );


  const shiftMap =
    {};


  shifts.forEach(
    function(shift) {

      shiftMap[
        String(
          shift.shift_id ||
          ''
        )
      ] =
        shift;

    }
  );


  const employeeMap =
    {};


  employees.forEach(
    function(employee) {

      employeeMap[
        String(
          employee.employee_id ||
          ''
        )
      ] =
        employee;

    }
  );


  /*
   * Jedna oprava může být v SHIFT_CHANGE_REQUESTS uložena
   * jako více řádků (např. clock_in + clock_out).
   * Seskládáme je podle request_id.
   */

  const groups =
    {};


  requests.forEach(
    function(request,index) {

      if (
        String(
          request.status ||
          ''
        ).trim() !==
        'PENDING'
      ) {

        return;

      }


      const groupId =
        String(
          request.request_id ||
          ''
        ) ||
        (
          'ROW_' +
          index
        );


      if (
        !groups[
          groupId
        ]
      ) {

        groups[
          groupId
        ] =
          {

            request_id:
              groupId,

            shift_id:
              String(
                request.shift_id ||
                ''
              ),

            employee_id:
              String(
                request.employee_id ||
                ''
              ),

            reason:
              String(
                request.reason ||
                ''
              ),

            created_at:
              String(
                request.created_at ||
                ''
              ),

            fields:
              [],

            rows:
              []

          };

      }


      groups[
        groupId
      ].rows.push(
        request
      );


      groups[
        groupId
      ].fields.push(
        {

          field:
            String(
              request.field ||
              ''
            ),

          old_value:
            String(
              request.old_value ||
              ''
            ),

          requested_value:
            String(
              request.requested_value ||
              ''
            )

        }
      );


      if (
        !groups[
          groupId
        ].reason &&
        request.reason
      ) {

        groups[
          groupId
        ].reason =
          String(
            request.reason
          );

      }

    }
  );


  const result =
    Object.keys(
      groups
    )
      .map(
        function(groupId) {

          const group =
            groups[
              groupId
            ];


          const shift =
            shiftMap[
              String(
                group.shift_id
              )
            ] ||
            null;


          const employeeId =
            group.employee_id ||
            (
              shift
                ? String(
                    shift.employee_id ||
                    ''
                  )
                : ''
            );


          const employee =
            employeeMap[
              employeeId
            ] ||
            null;


          const requested =
            attendanceCorrectionRequestedValues_(
              group.fields
            );


          return {

            request_id:
              group.request_id,

            shift_id:
              group.shift_id,

            employee_id:
              employeeId,

            employee_name:
              employee
                ? (
                    String(
                      employee.first_name ||
                      ''
                    ) +
                    ' ' +
                    String(
                      employee.last_name ||
                      ''
                    )
                  ).trim()
                : employeeId,

            work_date:
              shift
                ? String(
                    shift.work_date ||
                    ''
                  )
                : '',

            current_clock_in:
              shift
                ? String(
                    shift.clock_in ||
                    ''
                  )
                : '',

            current_clock_out:
              shift
                ? String(
                    shift.clock_out ||
                    ''
                  )
                : '',

            requested_clock_in:
              requested.clock_in,

            requested_clock_out:
              requested.clock_out,

            reason:
              group.reason,

            created_at:
              group.created_at,

            shift_status:
              shift
                ? String(
                    shift.status ||
                    ''
                  )
                : '',

            locked:
              shift
                ? String(
                    shift.status ||
                    ''
                  ).toUpperCase() ===
                    'LOCKED'
                : false

          };

        }
      )
      .sort(
        function(a,b) {

          return String(
            b.created_at ||
            ''
          ).localeCompare(
            String(
              a.created_at ||
              ''
            )
          );

        }
      );


  return {

    count:
      result.length,

    rows:
      result

  };

}



/* ============================================================
   SCHVÁLENÍ ŽÁDOSTI
============================================================ */

function approveAttendanceCorrection(
  token,
  requestId
) {

  const user =
    requireUser_(
      token,
      [
        'ADMIN',
        'MANAGER'
      ]
    );


  const lock =
    LockService
      .getScriptLock();


  lock.waitLock(
    10000
  );


  try {

    const requests =
      displayRows_(
        CFG.SHEETS.SHIFT_CHANGE_REQUESTS
      );


    const requestRows =
      requests.filter(
        function(request) {

          return (
            String(
              request.request_id ||
              ''
            ) ===
              String(
                requestId
              ) &&

            String(
              request.status ||
              ''
            ).trim() ===
              'PENDING'
          );

        }
      );


    if (
      !requestRows.length
    ) {

      throw new Error(
        'Žádost nebyla nalezena nebo už byla vyřešena.'
      );

    }


    const shiftId =
      String(
        requestRows[0].shift_id ||
        ''
      );


    const shifts =
      displayRows_(
        CFG.SHEETS.SHIFTS
      );


    const shift =
      shifts.find(
        function(row) {

          return (
            String(
              row.shift_id ||
              ''
            ) ===
            shiftId
          );

        }
      );


    if (
      !shift
    ) {

      throw new Error(
        'Směna uvedená v žádosti nebyla nalezena.'
      );

    }


    if (
      String(
        shift.status ||
        ''
      ).toUpperCase() ===
      'LOCKED'
    ) {

      throw new Error(
        'Tato směna je už uzamčená a předaná do mzdy. Nejdřív je nutné řešit mzdovou uzávěrku.'
      );

    }


    const requested =
      attendanceCorrectionRequestedValues_(
        requestRows.map(
          function(request) {

            return {

              field:
                request.field,

              old_value:
                request.old_value,

              requested_value:
                request.requested_value

            };

          }
        )
      );


    let newClockIn =
      requested.clock_in ||
      String(
        shift.clock_in ||
        ''
      );


    let newClockOut =
      requested.clock_out ||
      String(
        shift.clock_out ||
        ''
      );


    const clockInDate =
      parseAttendanceCorrectionDate_(
        newClockIn
      );


    const clockOutDate =
      parseAttendanceCorrectionDate_(
        newClockOut
      );


    if (
      !clockInDate
    ) {

      throw new Error(
        'Navrhovaný čas příchodu není platný.'
      );

    }


    if (
      newClockOut &&
      !clockOutDate
    ) {

      throw new Error(
        'Navrhovaný čas odchodu není platný.'
      );

    }


    if (
      clockOutDate &&
      clockOutDate <=
      clockInDate
    ) {

      throw new Error(
        'Odchod musí být později než příchod.'
      );

    }


    const breakMinutes =
      correctionNumber_(
        shift.break_minutes
      );


    let workedMinutes =
      correctionNumber_(
        shift.worked_minutes
      );


    let nightMinutes =
      correctionNumber_(
        shift.night_minutes
      );


    if (
      clockOutDate
    ) {

      workedMinutes =
        Math.max(
          0,
          Math.round(
            (
              clockOutDate.getTime() -
              clockInDate.getTime()
            ) /
            60000
          ) -
          breakMinutes
        );


      nightMinutes =
        calculateCorrectionNightMinutes_(
          clockInDate,
          clockOutDate
        );

    }


    const workDate =
      Utilities.formatDate(
        clockInDate,
        CFG.TZ,
        'yyyy-MM-dd'
      );


    const update =
      {

        clock_in:
          formatAttendanceCorrectionDate_(
            clockInDate
          ),

        work_date:
          workDate,

        worked_minutes:
          workedMinutes,

        night_minutes:
          nightMinutes,

        updated_at:
          now_()

      };


    if (
      clockOutDate
    ) {

      update.clock_out =
        formatAttendanceCorrectionDate_(
          clockOutDate
        );


      /*
       * Pokud měla směna odchod a není uzamčená,
       * má být po opravě uzavřená.
       */

      if (
        String(
          shift.status ||
          ''
        ) ===
        'OPEN'
      ) {

        update.status =
          'COMPLETED';

      }

    }


    updateBy_(
      CFG.SHEETS.SHIFTS,
      'shift_id',
      shiftId,
      update
    );


    requestRows.forEach(
      function(request) {

        updateBy_(
          CFG.SHEETS.SHIFT_CHANGE_REQUESTS,
          'request_id',
          request.request_id,
          {

            status:
              'APPROVED',

            resolved_at:
              now_(),

            resolved_by:
              user.user_id

          }
        );

      }
    );


    audit_(
      user.user_id,
      'ATTENDANCE_CORRECTION_APPROVED',
      'SHIFT',
      shiftId,
      {

        clock_in:
          shift.clock_in,

        clock_out:
          shift.clock_out,

        worked_minutes:
          shift.worked_minutes

      },
      {

        clock_in:
          update.clock_in,

        clock_out:
          update.clock_out ||
          shift.clock_out,

        worked_minutes:
          workedMinutes

      }
    );


    notifyEmployee_(
      shift.employee_id,
      'Oprava docházky schválena',
      'Tvoje žádost o opravu docházky ze dne ' +
      workDate +
      ' byla schválena.'
    );


    return {

      ok:
        true,

      shift_id:
        shiftId,

      worked_minutes:
        workedMinutes

    };

  }

  finally {

    lock.releaseLock();

  }

}



/* ============================================================
   ZAMÍTNUTÍ ŽÁDOSTI
============================================================ */

function rejectAttendanceCorrection(
  token,
  requestId
) {

  const user =
    requireUser_(
      token,
      [
        'ADMIN',
        'MANAGER'
      ]
    );


  const requests =
    displayRows_(
      CFG.SHEETS.SHIFT_CHANGE_REQUESTS
    );


  const requestRows =
    requests.filter(
      function(request) {

        return (
          String(
            request.request_id ||
            ''
          ) ===
            String(
              requestId
            ) &&

          String(
            request.status ||
            ''
          ).trim() ===
            'PENDING'
        );

      }
    );


  if (
    !requestRows.length
  ) {

    throw new Error(
      'Žádost nebyla nalezena nebo už byla vyřešena.'
    );

  }


  requestRows.forEach(
    function(request) {

      updateBy_(
        CFG.SHEETS.SHIFT_CHANGE_REQUESTS,
        'request_id',
        request.request_id,
        {

          status:
            'REJECTED',

          resolved_at:
            now_(),

          resolved_by:
            user.user_id

        }
      );

    }
  );


  const shiftId =
    String(
      requestRows[0].shift_id ||
      ''
    );


  const employeeId =
    String(
      requestRows[0].employee_id ||
      ''
    );


  audit_(
    user.user_id,
    'ATTENDANCE_CORRECTION_REJECTED',
    'SHIFT',
    shiftId,
    '',
    ''
  );


  if (
    employeeId
  ) {

    notifyEmployee_(
      employeeId,
      'Oprava docházky zamítnuta',
      'Tvoje žádost o opravu docházky byla zamítnuta.'
    );

  }


  return {
    ok:true
  };

}



/* ============================================================
   REQUESTED VALUES – ROBUSTNÍ PARSER
============================================================ */

function attendanceCorrectionRequestedValues_(
  fields
) {

  const result =
    {

      clock_in:
        '',

      clock_out:
        ''

    };


  (
    fields ||
    []
  ).forEach(
    function(item) {

      const field =
        String(
          item.field ||
          ''
        )
          .trim()
          .toLowerCase();


      const value =
        String(
          item.requested_value ||
          ''
        ).trim();


      if (
        field ===
        'clock_in'
      ) {

        result.clock_in =
          value;

        return;

      }


      if (
        field ===
        'clock_out'
      ) {

        result.clock_out =
          value;

        return;

      }


      /*
       * Pro jistotu podporujeme i případ, že requested_value
       * obsahuje JSON s oběma časy.
       */

      if (
        value &&
        (
          value.charAt(
            0
          ) ===
          '{'
        )
      ) {

        try {

          const parsed =
            JSON.parse(
              value
            );


          if (
            parsed.clock_in
          ) {

            result.clock_in =
              String(
                parsed.clock_in
              );

          }


          if (
            parsed.clock_out
          ) {

            result.clock_out =
              String(
                parsed.clock_out
              );

          }

        }

        catch(error) {

          /*
           * Není JSON, ignorujeme.
           */

        }

      }

    }
  );


  return result;

}



/* ============================================================
   DATUM / ČAS
============================================================ */

function parseAttendanceCorrectionDate_(
  value
) {

  if (
    !value
  ) {

    return null;

  }


  if (
    value instanceof Date
  ) {

    return value;

  }


  const text =
    String(
      value
    ).trim();


  let match =
    text.match(
      /^(\d{4})-(\d{2})-(\d{2})T(\d{1,2}):(\d{2})(?::(\d{2}))?$/
    );


  if (
    match
  ) {

    return new Date(
      Number(
        match[1]
      ),
      Number(
        match[2]
      ) - 1,
      Number(
        match[3]
      ),
      Number(
        match[4]
      ),
      Number(
        match[5]
      ),
      Number(
        match[6] ||
        0
      )
    );

  }


  match =
    text.match(
      /^(\d{4})-(\d{2})-(\d{2})\s+(\d{1,2}):(\d{2})(?::(\d{2}))?$/
    );


  if (
    match
  ) {

    return new Date(
      Number(
        match[1]
      ),
      Number(
        match[2]
      ) - 1,
      Number(
        match[3]
      ),
      Number(
        match[4]
      ),
      Number(
        match[5]
      ),
      Number(
        match[6] ||
        0
      )
    );

  }


  match =
    text.match(
      /^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/
    );


  if (
    match
  ) {

    return new Date(
      Number(
        match[3]
      ),
      Number(
        match[1]
      ) - 1,
      Number(
        match[2]
      ),
      Number(
        match[4] ||
        0
      ),
      Number(
        match[5] ||
        0
      ),
      Number(
        match[6] ||
        0
      )
    );

  }


  const date =
    new Date(
      text
    );


  return isNaN(
    date.getTime()
  )
    ? null
    : date;

}


function formatAttendanceCorrectionDate_(
  date
) {

  return Utilities.formatDate(
    date,
    CFG.TZ,
    'yyyy-MM-dd HH:mm:ss'
  );

}



/* ============================================================
   NOČNÍ MINUTY 22:00–06:00
============================================================ */

function calculateCorrectionNightMinutes_(
  start,
  end
) {

  if (
    !start ||
    !end ||
    end <=
    start
  ) {

    return 0;

  }


  let total =
    0;


  let cursor =
    new Date(
      start
    );


  cursor.setSeconds(
    0,
    0
  );


  while (
    cursor <
    end
  ) {

    const hour =
      cursor.getHours();


    if (
      hour >=
        22 ||
      hour <
        6
    ) {

      total++;

    }


    cursor =
      new Date(
        cursor.getTime() +
        60000
      );

  }


  return total;

}



/* ============================================================
   ČÍSLO
============================================================ */

function correctionNumber_(
  value
) {

  const number =
    Number(
      String(
        value == null
          ? 0
          : value
      )
        .replace(
          ',',
          '.'
        )
        .replace(
          /\s/g,
          ''
        )
    );


  return isFinite(
    number
  )
    ? number
    : 0;

}
