<?php

namespace App\Jobs;

use App\Employee;
use App\Payout;
use App\PointTable;
use App\ReportJob;
use App\Services\EmployeeService;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Bus\Batchable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

class GenerateExcelReportFund implements ShouldQueue
{
	use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
	
	protected $reportJob;
	protected $filters;
	protected $currentEmployeeId;
	
	/**
	 * The number of seconds the job can run before timing out.
	 *
	 * @var int
	 */
	public $timeout = 3600;
	
	/**
	 * Indicate if the job should be marked as failed on timeout.
	 *
	 * @var bool
	 */
	public $failOnTimeout = false;
	
	/**
	 * Create a new job instance.
	 *
	 * @param ReportJob $reportJob
	 * @param $filters
	 */
	public function __construct(ReportJob $reportJob, $filters)
	{
		$this->onQueue(config('queue.connections.redis.queue'));
		$this->reportJob = $reportJob;
		$this->filters = $filters;
	}
	
	/**
	 * @return bool
	 */
	public function isFailOnTimeout(): bool
	{
		return $this->failOnTimeout;
	}
	
	/**
	 * Execute the job.
	 *
	 * @return void
	 */
	public function handle()
	{
		try {
			ini_set('max_execution_time', 300);
			ini_set('memory_limit', '512M');
			
			$this->reportJob->update([
				'status'     => 'processing',
				'updated_at' => Carbon::now(),
			]);
			
			$templatePath = public_path('template/4.xlsx');
			// Suppress XML warnings from PhpSpreadsheet when loading template
			$previousErrorReporting = error_reporting(E_ERROR | E_PARSE);
			$spreadsheet = IOFactory::load($templatePath);
			error_reporting($previousErrorReporting);
			$sheet = $spreadsheet->getActiveSheet();
			
			$startYear = $this->filters['previous_years'];
			$endYear = $this->filters['years'];
			
			$employeeService = new EmployeeService();
			$payouts = Payout::select('years', 'rate')->orderBy('years')->get()->toArray();
			$pointTable = PointTable::select('years', 'points')->pluck('points', 'years')->toArray();
			$employees = Employee::with('department', 'transitionPoint', 'levelHistories', 'positionHistories')->select('*')->whereNotNull('join_date');
			
			/*if (isset($this->filters['years']) && $this->filters['years'] !== null && $this->filters['years'] !== "") {
				$startFiscalYear = Carbon::parse($this->filters['years'] . "-04-01");
				$endFiscalYear = Carbon::parse($this->filters['years'] . "-03-31")->addYear(1);
				$currentDate = Carbon::now();
				
				if ($currentDate->greaterThan($endFiscalYear)) {
					// Đã kết thúc năm tài chính.
					$employees->where(function ($query) use ($startFiscalYear, $endFiscalYear) {
						$query->where('employment_status', Employee::STATUS_RESIGNED)
							->where('resigned_date', '>=', $startFiscalYear->format('Y-m-d'))
							->where('resigned_date', '<=', $endFiscalYear->format('Y-m-d'));
					});
				} else {
					// Chưa kết thúc năm tài chính.
					$employees->where(function ($query) use ($startFiscalYear, $endFiscalYear) {
						$query->where('employment_status', Employee::STATUS_WORKING)
							->orWhere(function ($subQuery) use ($startFiscalYear, $endFiscalYear) {
								$subQuery->where('employment_status', Employee::STATUS_RESIGNED)
									->where('resigned_date', '>=', $startFiscalYear->format('Y-m-d'))
									->where('resigned_date', '<=', $endFiscalYear->format('Y-m-d'));
							});
					});
				}
			}*/
			
			if (isset($this->filters['employee_name']) && $this->filters['employee_name'] != null && $this->filters['employee_name'] != "") {
				$employees->where('full_name', 'like', '%' . $this->filters['employee_name'] . '%');
			}
			
			if (isset($this->filters['status']) && in_array($this->filters['status'], [1, 2, 3, 4])) {
				if ($this->filters['status'] == 2) {
					$employees->resigned();
				} elseif ($this->filters['status'] == 3) {
					$employees->leave();
				} elseif ($this->filters['status'] == 4) {
					$currentYear = Carbon::now()->year;
					$retirementAge = (integer)config('settings.retirement_age');
					$retirementAge = (!$retirementAge || $retirementAge < 10 || $retirementAge > 100) ? 60 : $retirementAge;
					$retirementBirthYear = $currentYear - ($retirementAge - 1);
					$startDate = Carbon::createFromDate($retirementBirthYear, 1, 1)->startOfDay();
					$endDate = Carbon::createFromDate($retirementBirthYear, 12, 31)->endOfDay();
					$employees->working()
						->whereNotNull('date_of_birth')
						->whereBetween('date_of_birth', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')]);
				} else {
					$employees->working();
				}
			}
			
			$employees = $employees->orderBy('id')->get();
			
			$row = 5;
			$totalAmount1 = 0;
			$totalAmount2 = 0;
			$totalDiffAmount = 0;
			
			foreach ($employees as $employee) {
				$this->currentEmployeeId = $employee->id;
				$joinDate = Carbon::parse($employee->join_date);
				$resignedDate = $employee->resigned_date ? Carbon::parse($employee->resigned_date) : null;
				$result = $this->calculateWorkingTime($employeeService, $employee, $payouts, $pointTable, $startYear, $endYear, $joinDate, $resignedDate);
				$startAmount = $result['start_amount'];
				$endAmount = $result['end_amount'];
				$diffAmount = $startAmount - $endAmount;
				if ($endAmount > $startAmount) $diffAmount = $endAmount - $startAmount;
				if ($diffAmount < 0) $diffAmount = 0;
				$fiscalEmployeeDateEnd = Carbon::parse($endYear . "-03-31");;
				if ($resignedDate && $fiscalEmployeeDateEnd->greaterThan($resignedDate)) {
					$fiscalEmployeeDateEnd = $resignedDate;
				}
				
				$sheet->setCellValue('A' . $row, $employee->department->code);
				$sheet->setCellValue('B' . $row, $employee->department->name);
				$sheet->setCellValue('C' . $row, $employee->employee_code);
				$sheet->setCellValue('D' . $row, $employee->full_name);
				$sheet->setCellValue('E' . $row, $employee->date_of_birth ? Carbon::parse($employee->date_of_birth)->format('Y/m/d') : '');
				$sheet->setCellValue('F' . $row, $joinDate ? $joinDate->format('Y/m/d') : '');
				$sheet->setCellValue('G' . $row, $fiscalEmployeeDateEnd->format('Y/m/d')); //$sheet->setCellValue('G' . $row, $resignedDate ? $resignedDate->format('Y/m/d') : '');
				$sheet->setCellValue('H' . $row, $startAmount);
				$sheet->setCellValue('I' . $row, $endAmount);
				$sheet->setCellValue('J' . $row, $diffAmount);
				
				$sheet->getStyle('H' . $row)->getNumberFormat()->setFormatCode('#,##0');
				$sheet->getStyle('I' . $row)->getNumberFormat()->setFormatCode('#,##0');
				$sheet->getStyle('J' . $row)->getNumberFormat()->setFormatCode('#,##0');
				
				$totalAmount1 += $startAmount;
				$totalAmount2 += $endAmount;
				$totalDiffAmount += $diffAmount;
				
				$sheet->getStyle('A' . $row . ':J' . $row)->getBorders()->getAllBorders()->setBorderStyle(Border::BORDER_THIN);
				$row++;
			}
			
			$sheet->setCellValue('A' . $row, "合計");
			$sheet->setCellValue('B' . $row, "");
			$sheet->setCellValue('C' . $row, "");
			$sheet->setCellValue('D' . $row, "");
			$sheet->setCellValue('E' . $row, "");
			$sheet->setCellValue('F' . $row, "");
			$sheet->setCellValue('G' . $row, "");
			$sheet->setCellValue('H' . $row, $totalAmount1);
			$sheet->setCellValue('I' . $row, $totalAmount2);
			$sheet->setCellValue('J' . $row, $totalDiffAmount);
			
			$sheet->setCellValue('H4', $startYear . "/3/31時点退職金");
			$sheet->setCellValue('I4', $endYear . "/3/31時点退職金");
			
			$sheet->getStyle('H' . $row)->getNumberFormat()->setFormatCode('#,##0');
			$sheet->getStyle('I' . $row)->getNumberFormat()->setFormatCode('#,##0');
			$sheet->getStyle('J' . $row)->getNumberFormat()->setFormatCode('#,##0');
			
			$lastRow = $row;
			$sheet->getStyle('A6:J' . $lastRow)->getBorders()->getAllBorders()->setBorderStyle(Border::BORDER_THIN);
			$sheet->setSelectedCell('A1');
			
			$savePath = public_path('report/' . $this->reportJob->code . '.xlsx');
			$writer = new Xlsx($spreadsheet);
			$writer->save($savePath);
			
			// Set Status
			$this->reportJob->update([
				'file'        => $savePath,
				'total_sheet' => $employees->count(),
				'status'      => 'success',
				'updated_at'  => Carbon::now(),
			]);
		} catch (\Exception $exception) {
			$this->reportJob->update([
				'status'     => 'failed',
				'updated_at' => Carbon::now(),
			]);
			Log::error("[GenerateExcelReportFund] Generate excel report one error [ID: {$this->currentEmployeeId}]: " . $exception->getMessage());
		}
	}
	
	private function calculateWorkingTime($employeeService, $employee, $payouts, $pointTable, $startYear, $endYear, $joinDate, $resignedDate)
	{
		$fiscalDateStart = Carbon::parse($startYear . "-03-31");
		$fiscalDateEnd = Carbon::parse($endYear . "-03-31");
		$startAmount = 0;
		$endAmount = 0;

		// Với nhân viên chưa nghỉ việc
		if (!$resignedDate) {
			if ($joinDate->lessThanOrEqualTo($fiscalDateEnd)) {
				if ($joinDate->lessThanOrEqualTo($fiscalDateStart)) {
					// Case: Có thể tính cả 2 mốc thời gian
					$resultStart = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $startYear);
					$resultEnd = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $endYear);
					$startAmount = $resultStart['total_amount'];
					$endAmount = $resultEnd['total_amount'];
				} else {
					// Case: Chỉ tính được mốc thời gian thứ 2
					$resultEnd = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $endYear);
					$endAmount = $resultEnd['total_amount'];
				}
			}
		} else { // Với nhân viên đã nghỉ việc
			if ($resignedDate->lessThan($fiscalDateStart)) {
				// Case: Đã nghỉ việc trước cả 2 mốc thời gian
				$calculatedYear = $resignedDate->greaterThan(Carbon::parse($resignedDate->year . "-03-31")) ? $resignedDate->year + 1 : $resignedDate->year;
				$resultBoth = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $calculatedYear);
				$startAmount = $resultBoth['total_amount'];
				$endAmount = $resultBoth['total_amount'];
			} elseif ($joinDate->lessThanOrEqualTo($fiscalDateStart)) {
				// Tính mốc thời gian đầu
				$resultStart = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $startYear);
				$startAmount = $resultStart['total_amount'];
				
				// Tính mốc thời gian sau
				if ($resignedDate->greaterThanOrEqualTo($fiscalDateEnd)) {
					// Còn làm việc tại mốc thời gian thứ 2
					$resultEnd = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $endYear);
					$endAmount = $resultEnd['total_amount'];
				} else {
					// Đã nghỉ việc trước mốc thời gian thứ 2
					$calculatedYear = $resignedDate->greaterThan(Carbon::parse($resignedDate->year . "-03-31")) ? $resignedDate->year + 1 : $resignedDate->year;
					$resultEnd = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $calculatedYear);
					$endAmount = $resultEnd['total_amount'];
				}
			} elseif ($joinDate->lessThanOrEqualTo($fiscalDateEnd)) {
				// Case: Chỉ tính được từ thời điểm nghỉ việc
				$calculatedYear = $resignedDate->greaterThan(Carbon::parse($resignedDate->year . "-03-31")) ? $resignedDate->year + 1 : $resignedDate->year;
				$resultEnd = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $calculatedYear);
				$endAmount = $resultEnd['total_amount'];
			}
		}
		
		return [
			'start_amount' => $startAmount,
			'end_amount'   => $endAmount
		];
	}
	
	private function calculateWorkingTimeFullCase($employeeService, $employee, $payouts, $pointTable, $startYear, $endYear, $joinDate, $resignedDate)
	{
		$fiscalDateStart = Carbon::parse($startYear . "-03-31");
		$fiscalDateEnd = Carbon::parse($endYear . "-03-31");
		$startAmount = 0;
		$endAmount = 0;
		
		// Với nhân viên chưa nghỉ việc (resigned_date = null)
		if (!$resignedDate) {
			if ($joinDate->greaterThan($fiscalDateEnd)) {
				// TH1: Cả 2 mốc thời gian đều trước ngày bắt đầu làm việc
				// Không tính được vì chưa bắt đầu làm việc
				// Không xử lý gì cả.
			} elseif ($joinDate->greaterThan($fiscalDateStart) && $joinDate->lessThanOrEqualTo($fiscalDateEnd)) {
				// TH2: Mốc thời gian đầu trước join_date, mốc sau sau join_date
				// Chỉ tính được cho mốc thời gian thứ 2
				$resultEnd = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $endYear);
				$endAmount = $resultEnd['total_amount'];
			} else {
				// TH3: Cả 2 mốc thời gian đều sau join_date (case chuẩn)
				// Tính được cả 2 mốc thời gian
				$resultStart = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $startYear);
				$resultEnd = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $endYear);
				$startAmount = $resultStart['total_amount'];
				$endAmount = $resultEnd['total_amount'];
			}
		} else {  // Với nhân viên đã nghỉ việc (có resigned_date)
			if ($joinDate->greaterThan($fiscalDateEnd)) {
				// TH1: Cả 2 mốc thời gian đều trước join_date
				// Không tính được vì chưa bắt đầu làm việc
				// Không xử lý gì cả.
			} elseif ($resignedDate->lessThan($fiscalDateStart)) {
				// TH2: Cả 2 mốc thời gian đều sau resigned_date
				// Tính toán từ thời gian bắt đầu làm việc đến năm nghỉ việc.
				
				// Nếu ngày nghỉ việc sau 31/03 của năm đó thì giới hạn tính toán là ngày 31/03 của năm tiếp theo
				if ($resignedDate->greaterThan(Carbon::parse($resignedDate->year . "-03-31"))) {
					$resultBoth = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, ($resignedDate->year + 1));
					$startAmount = $resultBoth['total_amount'];
					$endAmount = $resultBoth['total_amount'];
				} else {
					$resultBoth = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $resignedDate->year);
					$startAmount = $resultBoth['total_amount'];
					$endAmount = $resultBoth['total_amount'];
				}
			} elseif ($joinDate->greaterThan($fiscalDateStart) && $joinDate->lessThanOrEqualTo($fiscalDateEnd) && $resignedDate->greaterThanOrEqualTo($fiscalDateEnd)) {
				// TH3: Mốc đầu trước join_date, mốc sau trong khoảng làm việc
				// Chỉ tính được cho mốc thời gian thứ 2
				$resultEnd = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $endYear);
				$endAmount = $resultEnd['total_amount'];
			} elseif ($joinDate->lessThanOrEqualTo($fiscalDateStart) && $resignedDate->greaterThanOrEqualTo($fiscalDateStart) && $resignedDate->lessThan($fiscalDateEnd)) {
				// TH4: Mốc đầu trong khoảng làm việc, mốc sau sau resigned_date
				// Tính được cho mốc thời gian thứ 1 và mốc thời gian thứ 2 sẽ tính đến năm nghỉ việc.
				$resultStart = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $startYear);
				$startAmount = $resultStart['total_amount'];
				
				// Tính toán từ ngày làm việc đến ngày nghỉ cho mốc sau.
				// Nếu ngày nghỉ việc sau 31/03 của năm đó thì giới hạn tính toán là ngày 31/03 của năm tiếp theo
				if ($resignedDate->greaterThan(Carbon::parse($resignedDate->year . "-03-31"))) {
					$resultEnd = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, ($resignedDate->year + 1));
					$endAmount = $resultEnd['total_amount'];
				} else {
					$resultEnd = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $resignedDate->year);
					$endAmount = $resultEnd['total_amount'];
				}
			} elseif ($joinDate->lessThanOrEqualTo($fiscalDateStart) && $resignedDate->greaterThanOrEqualTo($fiscalDateEnd)) {
				// TH5: Cả 2 mốc thời gian đều trong khoảng làm việc (case chuẩn)
				// Tính được cả 2 mốc thời gian
				$resultStart = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $startYear);
				$resultEnd = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $endYear);
				$startAmount = $resultStart['total_amount'];
				$endAmount = $resultEnd['total_amount'];
			} elseif ($joinDate->greaterThan($fiscalDateStart) && $resignedDate->lessThan($fiscalDateEnd)) {
				// TH6: Mốc đầu trước join_date, mốc sau sau resigned_date
				// Tính toán mốc thời gian kết thúc tính đến ngày nhân viên nghỉ việc.
				
				if ($resignedDate->greaterThan(Carbon::parse($resignedDate->year . "-03-31"))) {
					$resultEnd = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, ($resignedDate->year + 1));
					$endAmount = $resultEnd['total_amount'];
				} else {
					$resultEnd = $employeeService->calculateSeverancePayment($employee, $payouts, $pointTable, $resignedDate->year);
					$endAmount = $resultEnd['total_amount'];
				}
			}
		}
		
		return [
			'start_amount' => $startAmount,
			'end_amount'   => $endAmount
		];
	}
}
