/* ============================================================
   EATME PORTÁL – PAYROLL SERVICE

   Role:
   - MANAGER / ADMIN: spropitné, bonusy, jiné položky před uzamčením
   - ACCOUNTANT / ADMIN: účetní zpracování mzdy
============================================================ */


/* ============================================================
   MANAGER / ADMIN – NAČTENÍ ODMĚN PRO KONKRÉTNÍ MĚSÍC
============================================================ */

function getPayrollAdjustments(
  token,
  employeeId,
  period
) {

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


  period =
    String(
      period || ''
    ).trim();


  if (
    !/^\d{4}-\d{2}$/.test(
      period
    )
  ) {

    throw new Error(
      'Neplatné období.'
    );

  }


  const payrolls =
    displayRows_(
      CFG.SHEETS.PAYROLL
    );


  const payroll =
    payrolls.find(
      function(row) {

        return (
          String(
            row.employee_id
          ) ===
          String(
            employeeId
          ) &&
          String(
            row.period
          ) ===
          period
        );

      }
    );


  const summary =
    getClosureSummary_(
      employeeId,
      period
    );


  return {

    payroll_id:
      payroll
        ? String(
            payroll.payroll_id || ''
          )
        : '',

    employee_id:
      String(
        employeeId || ''
      ),

    period:
      period,

    base_amount:
      Number(
        summary.earned_estimate ||
        0
      ),

    tips_amount:
      payroll
        ? numberFromSheet_(
            payroll.tips_amount
          )
        : 0,

    bonus_amount:
      payroll
        ? numberFromSheet_(
            payroll.bonus_amount
          )
        : 0,

    other_amount:
      payroll
        ? numberFromSheet_(
            payroll.other_amount
          )
        : 0,

    final_amount:
      payroll
        ? numberFromSheet_(
            payroll.final_amount
          )
        : Number(
            summary.earned_estimate ||
            0
          ),

    status:
      payroll
        ? String(
            payroll.status || ''
          )
        : 'DRAFT_MANAGER'

  };

}



/* ============================================================
   MANAGER / ADMIN – ULOŽENÍ SPROPITNÉHO A ODMĚN
============================================================ */

function savePayrollAdjustments(
  token,
  employeeId,
  period,
  tipsAmount,
  bonusAmount,
  otherAmount
) {

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


  period =
    String(
      period || ''
    ).trim();


  if (
    !/^\d{4}-\d{2}$/.test(
      period
    )
  ) {

    throw new Error(
      'Neplatné období.'
    );

  }


  tipsAmount =
    payrollNumber_(
      tipsAmount
    );


  bonusAmount =
    payrollNumber_(
      bonusAmount
    );


  otherAmount =
    payrollNumber_(
      otherAmount
    );


  const closures =
    displayRows_(
      CFG.SHEETS.MONTH_CLOSURES
    );


  const closure =
    closures.find(
      function(row) {

        return (
          String(
            row.employee_id
          ) ===
          String(
            employeeId
          ) &&
          String(
            row.period
          ) ===
          period
        );

      }
    );


  if (
    !closure
  ) {

    throw new Error(
      'Měsíční uzávěrka nebyla nalezena.'
    );

  }


  if (
    String(
      closure.status
    ) ===
    'LOCKED'
  ) {

    throw new Error(
      'Uzamčenou mzdu už nelze měnit.'
    );

  }


  if (
    ![
      'EMPLOYEE_CONFIRMED',
      'MANAGER_APPROVED'
    ].includes(
      String(
        closure.status
      )
    )
  ) {

    throw new Error(
      'Odměny lze zadat až po potvrzení docházky zaměstnancem.'
    );

  }


  const summary =
    getClosureSummary_(
      employeeId,
      period
    );


  const baseAmount =
    Number(
      summary.earned_estimate ||
      0
    );


  const finalAmount =
    baseAmount +
    tipsAmount +
    bonusAmount +
    otherAmount;


  const payrolls =
    displayRows_(
      CFG.SHEETS.PAYROLL
    );


  const existing =
    payrolls.find(
      function(row) {

        return (
          String(
            row.employee_id
          ) ===
          String(
            employeeId
          ) &&
          String(
            row.period
          ) ===
          period
        );

      }
    );


  if (
    existing &&
    [
      'READY_FOR_ACCOUNTANT',
      'PROCESSING',
      'PROCESSED',
      'PAID'
    ].includes(
      String(
        existing.status
      )
    )
  ) {

    throw new Error(
      'Mzdový podklad už byl předán účetní a nelze jej tímto způsobem měnit.'
    );

  }


  if (
    existing
  ) {

    updateBy_(
      CFG.SHEETS.PAYROLL,
      'payroll_id',
      existing.payroll_id,
      {

        approved_minutes:
          summary.worked_minutes,

        base_amount:
          baseAmount,

        tips_amount:
          tipsAmount,

        bonus_amount:
          bonusAmount,

        other_amount:
          otherAmount,

        final_amount:
          finalAmount,

        status:
          'DRAFT_MANAGER',

        updated_at:
          now_()

      }
    );

  } else {

    append_(
      CFG.SHEETS.PAYROLL,
      {

        payroll_id:
          uuid_(
            'PAY'
          ),

        employee_id:
          employeeId,

        period:
          period,

        approved_minutes:
          summary.worked_minutes,

        base_amount:
          baseAmount,

        bonus_amount:
          bonusAmount,

        tips_amount:
          tipsAmount,

        other_amount:
          otherAmount,

        final_amount:
          finalAmount,

        status:
          'DRAFT_MANAGER',

        payment_date:
          '',

        updated_at:
          now_()

      }
    );

  }


  audit_(
    user.user_id,
    'PAYROLL_ADJUSTMENTS_SAVED',
    'PAYROLL',
    String(
      employeeId
    ) +
    ':' +
    period,
    '',
    JSON.stringify({

      tips_amount:
        tipsAmount,

      bonus_amount:
        bonusAmount,

      other_amount:
        otherAmount,

      final_amount:
        finalAmount

    })
  );


  return {

    ok:
      true,

    final_amount:
      finalAmount

  };

}



/* ============================================================
   ADMIN – UZAMČENÍ A PŘEDÁNÍ ÚČETNÍ
   Tato funkce nahrazuje původní lockMonthClosure z frontendu.
============================================================ */

function adminLockClosureAndPreparePayroll(
  token,
  closureId
) {

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


  const lock =
    LockService
      .getScriptLock();


  lock.waitLock(
    10000
  );


  try {

    const closures =
      displayRows_(
        CFG.SHEETS.MONTH_CLOSURES
      );


    const closure =
      closures.find(
        function(row) {

          return (
            String(
              row.closure_id
            ) ===
            String(
              closureId
            )
          );

        }
      );


    if (
      !closure
    ) {

      throw new Error(
        'Uzávěrka nebyla nalezena.'
      );

    }


    if (
      String(
        closure.status
      ) !==
      'MANAGER_APPROVED'
    ) {

      throw new Error(
        'Uzávěrku lze předat účetní až po schválení.'
      );

    }


    const summary =
      getClosureSummary_(
        closure.employee_id,
        closure.period
      );


    const payrolls =
      displayRows_(
        CFG.SHEETS.PAYROLL
      );


    const existing =
      payrolls.find(
        function(row) {

          return (
            String(
              row.employee_id
            ) ===
            String(
              closure.employee_id
            ) &&
            String(
              row.period
            ) ===
            String(
              closure.period
            )
          );

        }
      );


    const tipsAmount =
      existing
        ? numberFromSheet_(
            existing.tips_amount
          )
        : 0;


    const bonusAmount =
      existing
        ? numberFromSheet_(
            existing.bonus_amount
          )
        : 0;


    const otherAmount =
      existing
        ? numberFromSheet_(
            existing.other_amount
          )
        : 0;


    const baseAmount =
      Number(
        summary.earned_estimate ||
        0
      );


    const finalAmount =
      baseAmount +
      tipsAmount +
      bonusAmount +
      otherAmount;


    if (
      existing
    ) {

      updateBy_(
        CFG.SHEETS.PAYROLL,
        'payroll_id',
        existing.payroll_id,
        {

          approved_minutes:
            summary.worked_minutes,

          base_amount:
            baseAmount,

          tips_amount:
            tipsAmount,

          bonus_amount:
            bonusAmount,

          other_amount:
            otherAmount,

          final_amount:
            finalAmount,

          status:
            'READY_FOR_ACCOUNTANT',

          updated_at:
            now_()

        }
      );

    } else {

      append_(
        CFG.SHEETS.PAYROLL,
        {

          payroll_id:
            uuid_(
              'PAY'
            ),

          employee_id:
            closure.employee_id,

          period:
            closure.period,

          approved_minutes:
            summary.worked_minutes,

          base_amount:
            baseAmount,

          bonus_amount:
            bonusAmount,

          tips_amount:
            tipsAmount,

          other_amount:
            otherAmount,

          final_amount:
            finalAmount,

          status:
            'READY_FOR_ACCOUNTANT',

          payment_date:
            '',

          updated_at:
            now_()

        }
      );

    }


    updateBy_(
      CFG.SHEETS.MONTH_CLOSURES,
      'closure_id',
      closureId,
      {

        locked_at:
          now_(),

        status:
          'LOCKED'

      }
    );


    setMonthShiftStatus_(
      closure.employee_id,
      closure.period,
      'LOCKED'
    );


    audit_(
      user.user_id,
      'MONTH_LOCKED_AND_SENT_TO_ACCOUNTANT',
      'MONTH_CLOSURE',
      closureId,
      'MANAGER_APPROVED',
      'LOCKED'
    );


    notifyEmployee_(
      closure.employee_id,
      'Docházka uzavřena',
      'Docházka za ' +
      closure.period +
      ' byla uzavřena a předána ke mzdovému zpracování.'
    );


    return {

      ok:
        true,

      final_amount:
        finalAmount

    };

  }

  finally {

    lock.releaseLock();

  }

}



/* ============================================================
   ACCOUNTANT / ADMIN – MZDOVÝ DASHBOARD
============================================================ */

function getPayrollDashboard(
  token,
  period
) {

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


  period =
    String(
      period || ''
    ).trim();


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


  const employeeMap =
    {};


  employees.forEach(
    function(employee) {

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

    }
  );


  let payrolls =
    displayRows_(
      CFG.SHEETS.PAYROLL
    );


  if (
    period
  ) {

    payrolls =
      payrolls.filter(
        function(row) {

          return (
            String(
              row.period
            ) ===
            period
          );

        }
      );

  }


  payrolls =
    payrolls.filter(
      function(row) {

        return [
          'READY_FOR_ACCOUNTANT',
          'PROCESSING',
          'PROCESSED',
          'PAID'
        ].includes(
          String(
            row.status
          )
        );

      }
    );


  const rows =
    payrolls
      .map(
        function(payroll) {

          const employee =
            employeeMap[
              String(
                payroll.employee_id
              )
            ];


          const baseAmount =
            numberFromSheet_(
              payroll.base_amount
            );


          const tipsAmount =
            numberFromSheet_(
              payroll.tips_amount
            );


          const bonusAmount =
            numberFromSheet_(
              payroll.bonus_amount
            );


          const otherAmount =
            numberFromSheet_(
              payroll.other_amount
            );


          const finalAmount =
            baseAmount +
            tipsAmount +
            bonusAmount +
            otherAmount;


          return {

            payroll_id:
              String(
                payroll.payroll_id ||
                ''
              ),

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

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

            employment_type:
              employee
                ? String(
                    employee.employment_type ||
                    ''
                  )
                : '',

            period:
              String(
                payroll.period ||
                ''
              ),

            approved_minutes:
              numberFromSheet_(
                payroll.approved_minutes
              ),

            base_amount:
              baseAmount,

            tips_amount:
              tipsAmount,

            bonus_amount:
              bonusAmount,

            other_amount:
              otherAmount,

            final_amount:
              finalAmount,

            status:
              String(
                payroll.status ||
                ''
              ),

            payment_date:
              String(
                payroll.payment_date ||
                ''
              )

          };

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

          if (
            a.period !==
            b.period
          ) {

            return b.period.localeCompare(
              a.period
            );

          }


          return a.employee_name.localeCompare(
            b.employee_name,
            'cs'
          );

        }
      );


  const stats = {

    count:
      rows.length,

    total_base:
      0,

    total_tips:
      0,

    total_bonus:
      0,

    total_other:
      0,

    total_final:
      0,

    ready:
      0,

    processing:
      0,

    processed:
      0,

    paid:
      0

  };


  rows.forEach(
    function(row) {

      stats.total_base +=
        row.base_amount;


      stats.total_tips +=
        row.tips_amount;


      stats.total_bonus +=
        row.bonus_amount;


      stats.total_other +=
        row.other_amount;


      stats.total_final +=
        row.final_amount;


      if (
        row.status ===
        'READY_FOR_ACCOUNTANT'
      ) {

        stats.ready++;

      }


      if (
        row.status ===
        'PROCESSING'
      ) {

        stats.processing++;

      }


      if (
        row.status ===
        'PROCESSED'
      ) {

        stats.processed++;

      }


      if (
        row.status ===
        'PAID'
      ) {

        stats.paid++;

      }

    }
  );


  return {

    role:
      String(
        user.role ||
        ''
      ),

    period:
      period,

    stats:
      stats,

    rows:
      rows

  };

}



/* ============================================================
   ACCOUNTANT / ADMIN – ZMĚNA STAVU
============================================================ */

function setPayrollStatus(
  token,
  payrollId,
  newStatus
) {

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


  newStatus =
    String(
      newStatus || ''
    ).trim();


  const allowed =
    [
      'READY_FOR_ACCOUNTANT',
      'PROCESSING',
      'PROCESSED',
      'PAID'
    ];


  if (
    !allowed.includes(
      newStatus
    )
  ) {

    throw new Error(
      'Neplatný stav mzdy.'
    );

  }


  const payrolls =
    displayRows_(
      CFG.SHEETS.PAYROLL
    );


  const payroll =
    payrolls.find(
      function(row) {

        return (
          String(
            row.payroll_id
          ) ===
          String(
            payrollId
          )
        );

      }
    );


  if (
    !payroll
  ) {

    throw new Error(
      'Mzdový podklad nebyl nalezen.'
    );

  }


  const currentStatus =
    String(
      payroll.status ||
      ''
    );


  const order = {

    READY_FOR_ACCOUNTANT:
      1,

    PROCESSING:
      2,

    PROCESSED:
      3,

    PAID:
      4

  };


  if (
    order[
      newStatus
    ] <
    order[
      currentStatus
    ]
  ) {

    throw new Error(
      'Stav mzdy nelze vracet zpět.'
    );

  }


  const update = {

    status:
      newStatus,

    updated_at:
      now_()

  };


  if (
    newStatus ===
    'PAID'
  ) {

    update.payment_date =
      Utilities.formatDate(
        new Date(),
        CFG.TZ,
        'yyyy-MM-dd'
      );

  }


  updateBy_(
    CFG.SHEETS.PAYROLL,
    'payroll_id',
    payrollId,
    update
  );


  audit_(
    user.user_id,
    'PAYROLL_STATUS_CHANGED',
    'PAYROLL',
    payrollId,
    currentStatus,
    newStatus
  );


  if (
    newStatus ===
    'PROCESSED'
  ) {

    notifyEmployee_(
      payroll.employee_id,
      'Mzda byla zpracována',
      'Mzda za ' +
      payroll.period +
      ' byla zpracována účetní.'
    );

  }


  if (
    newStatus ===
    'PAID'
  ) {

    notifyEmployee_(
      payroll.employee_id,
      'Mzda byla označena jako vyplacená',
      'Mzda za ' +
      payroll.period +
      ' byla označena jako vyplacená.'
    );

  }


  return {

    ok:
      true

  };

}



/* ============================================================
   ACCOUNTANT / ADMIN – CSV EXPORT
============================================================ */

function exportPayrollCsv(
  token,
  period
) {

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


  const data =
    getPayrollDashboard(
      token,
      period
    );


  const rows =
    [
      [
        'Zaměstnanec',
        'Typ vztahu',
        'Období',
        'Schválené hodiny',
        'Základ',
        'Spropitné',
        'Bonusy',
        'Ostatní',
        'Celkem',
        'Stav',
        'Datum výplaty'
      ]
    ];


  data.rows.forEach(
    function(row) {

      rows.push(
        [
          row.employee_name,
          row.employment_type,
          row.period,
          (
            row.approved_minutes /
            60
          ).toFixed(
            2
          ),
          row.base_amount,
          row.tips_amount,
          row.bonus_amount,
          row.other_amount,
          row.final_amount,
          row.status,
          row.payment_date
        ]
      );

    }
  );


  const csv =
    rows
      .map(
        function(row) {

          return row
            .map(
              function(value) {

                const text =
                  String(
                    value == null
                      ? ''
                      : value
                  );


                return (
                  '"' +
                  text.replace(
                    /"/g,
                    '""'
                  ) +
                  '"'
                );

              }
            )
            .join(
              ';'
            );

        }
      )
      .join(
        '\r\n'
      );


  return {

    filename:
      'EatMe_Portal_mzdy_' +
      (
        period ||
        'vse'
      ) +
      '.csv',

    content:
      '\uFEFF' +
      csv

  };

}



/* ============================================================
   POMOCNÁ FUNKCE – ČÍSELNÁ HODNOTA
============================================================ */

function payrollNumber_(
  value
) {

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


  if (
    !isFinite(
      number
    )
  ) {

    throw new Error(
      'Částka musí být číslo.'
    );

  }


  return Math.round(
    number *
    100
  ) /
  100;

}



/* ============================================================
   DIAGNOSTIKA
============================================================ */

function testPayrollDatabase() {

  Logger.log(
    'PAYROLL: ' +
    JSON.stringify(
      displayRows_(
        CFG.SHEETS.PAYROLL
      )
    )
  );


  Logger.log(
    'PayrollService OK'
  );

}