<?php
declare(strict_types=1);

namespace App\Modules\HR\Controllers;

use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Core\App;
use App\Core\Logger;
use App\Modules\HR\Models\HrPayrollPeriod;
use App\Modules\HR\Models\HrPayrollRun;
use App\Modules\HR\Models\HrEmployeeProfile;
use App\Modules\HR\Services\PayrollCalculationService;
use App\Modules\HR\Services\SalarySlipService;
use App\Core\EventBus;

class PayrollController extends Controller
{
    public function index(Request $request): Response
    {
        return $this->redirect('/hr/payroll/periods');
    }

    public function periods(Request $request): Response
    {
        $filters = [
            'year'   => trim((string) $request->get('year', (string) date('Y'))),
            'status' => trim((string) $request->get('status', '')),
        ];

        $page = max(1, (int) $request->get('page', 1));
        $result = HrPayrollPeriod::search($filters, 25, $page);

        return $this->view('HR.Views.payroll.periods', [
            'periods'    => $result['data'],
            'pagination' => $result['pagination'],
            'filters'    => $filters,
        ]);
    }

    public function createPeriod(Request $request): Response
    {
        $year = (int) $request->post('year', (int) date('Y'));
        $month = (int) $request->post('month', (int) date('m'));

        if ($year < 2020 || $year > 2099 || $month < 1 || $month > 12) {
            return $this->redirect('/hr/payroll/periods')->withError('تاريخ الفترة غير صالح');
        }

        $db = App::getInstance()->db();
        $employee = $this->currentEmployee();

        // Check if period already exists
        $existing = $db->selectOne(
            "SELECT id FROM hr_payroll_periods WHERE year = ? AND month = ? AND is_archived = 0",
            [$year, $month]
        );

        if ($existing) {
            return $this->redirect('/hr/payroll/periods/' . $existing['id'])->withError('فترة الرواتب موجودة بالفعل');
        }

        $periodCode = sprintf('PAY-%04d-%02d', $year, $month);
        $startDate = sprintf('%04d-%02d-01', $year, $month);
        $endDate = date('Y-m-t', strtotime($startDate));

        $periodId = $db->insert('hr_payroll_periods', [
            'period_code' => $periodCode,
            'year'        => $year,
            'month'       => $month,
            'start_date'  => $startDate,
            'end_date'    => $endDate,
            'status'      => 'open',
            'created_at'  => date('Y-m-d H:i:s'),
            'updated_at'  => date('Y-m-d H:i:s'),
            'created_by'  => $employee ? (int) $employee->id : null,
        ]);

        Logger::info("Payroll period created", ['period_id' => $periodId, 'code' => $periodCode]);

        return $this->redirect('/hr/payroll/periods/' . $periodId)->withSuccess('تم إنشاء فترة الرواتب بنجاح');
    }

    public function showPeriod(Request $request, string $id): Response
    {
        $period = HrPayrollPeriod::find((int) $id);
        if (!$period) {
            return $this->redirect('/hr/payroll/periods')->withError('فترة الرواتب غير موجودة');
        }

        $runs = HrPayrollRun::getForPeriod((int) $id);

        $db = App::getInstance()->db();
        $totals = $db->selectOne(
            "SELECT COUNT(*) as employee_count,
                    SUM(gross_earnings) as total_gross,
                    SUM(net_salary) as total_net,
                    SUM(total_deductions) as total_deductions
             FROM hr_payroll_runs
             WHERE period_id = ? AND is_archived = 0",
            [(int) $id]
        );

        return $this->view('HR.Views.payroll.show_period', [
            'period' => $period,
            'runs'   => $runs,
            'totals' => $totals,
        ]);
    }

    public function calculate(Request $request, string $id): Response
    {
        $period = HrPayrollPeriod::find((int) $id);
        if (!$period) {
            return $this->redirect('/hr/payroll/periods')->withError('فترة الرواتب غير موجودة');
        }

        if (!in_array($period->status, ['open', 'calculated'], true)) {
            return $this->redirect('/hr/payroll/periods/' . $id)->withError('لا يمكن حساب رواتب فترة بحالة: ' . $period->status);
        }

        $result = PayrollCalculationService::calculateForPeriod((int) $id);

        if (!$result['success']) {
            return $this->redirect('/hr/payroll/periods/' . $id)->withError($result['error'] ?? 'فشل حساب الرواتب');
        }

        $msg = sprintf(
            'تم حساب رواتب %d موظف بنجاح (إجمالي صافي: %s ج.م)',
            $result['processed_count'] ?? 0,
            number_format((float) ($result['total_net'] ?? 0), 2)
        );

        if (!empty($result['errors'])) {
            $msg .= ' - ' . count($result['errors']) . ' أخطاء';
        }

        return $this->redirect('/hr/payroll/periods/' . $id)->withSuccess($msg);
    }

    public function approve(Request $request, string $id): Response
    {
        $period = HrPayrollPeriod::find((int) $id);
        if (!$period) {
            return $this->redirect('/hr/payroll/periods')->withError('فترة الرواتب غير موجودة');
        }

        if ($period->status !== 'calculated') {
            return $this->redirect('/hr/payroll/periods/' . $id)->withError('يجب حساب الرواتب أولاً قبل الاعتماد');
        }

        $db = App::getInstance()->db();
        $employee = $this->currentEmployee();

        $db->update('hr_payroll_periods', [
            'status'      => 'approved',
            'approved_by' => $employee ? (int) $employee->id : null,
            'approved_at' => date('Y-m-d H:i:s'),
            'updated_at'  => date('Y-m-d H:i:s'),
        ], '`id` = ?', [(int) $id]);

        Logger::info("Payroll period approved", ['period_id' => (int) $id]);

        return $this->redirect('/hr/payroll/periods/' . $id)->withSuccess('تم اعتماد الرواتب بنجاح');
    }

    public function markPaid(Request $request, string $id): Response
    {
        $period = HrPayrollPeriod::find((int) $id);
        if (!$period) {
            return $this->redirect('/hr/payroll/periods')->withError('فترة الرواتب غير موجودة');
        }

        if ($period->status !== 'approved') {
            return $this->redirect('/hr/payroll/periods/' . $id)->withError('يجب اعتماد الرواتب أولاً قبل الصرف');
        }

        $db = App::getInstance()->db();
        $employee = $this->currentEmployee();
        $paymentDate = trim((string) $request->post('payment_date', date('Y-m-d')));

        $db->beginTransaction();
        try {
            $db->update('hr_payroll_periods', [
                'status'       => 'paid',
                'paid_date'    => $paymentDate,
                'updated_at'   => date('Y-m-d H:i:s'),
                'updated_by'   => $employee ? (int) $employee->id : null,
            ], '`id` = ?', [(int) $id]);

            // Mark all runs as paid
            $db->update('hr_payroll_runs', [
                'status'         => 'paid',
                'paid_at'        => $paymentDate . ' ' . date('H:i:s'),
                'updated_at'     => date('Y-m-d H:i:s'),
            ], '`period_id` = ? AND `is_archived` = 0', [(int) $id]);

            $db->commit();

            // Dispatch payroll paid event for each run — triggers Accounting auto-post
            $runs = $db->select(
                "SELECT id FROM hr_payroll_runs WHERE period_id = ? AND is_archived = 0",
                [(int) $id]
            );
            foreach ($runs as $run) {
                EventBus::dispatch('hr.payroll.paid', [
                    'payroll_run_id' => (int) $run['id'],
                    'period_id'      => (int) $id,
                    'payment_date'   => $paymentDate,
                ]);
            }

            Logger::info("Payroll period marked paid", ['period_id' => (int) $id, 'payment_date' => $paymentDate]);

            return $this->redirect('/hr/payroll/periods/' . $id)->withSuccess('تم صرف الرواتب بنجاح');

        } catch (\Throwable $e) {
            $db->rollBack();
            Logger::error("Payroll payment failed: " . $e->getMessage());
            return $this->redirect('/hr/payroll/periods/' . $id)->withError('فشل صرف الرواتب: ' . $e->getMessage());
        }
    }

    public function closePeriod(Request $request, string $id): Response
    {
        $period = HrPayrollPeriod::find((int) $id);
        if (!$period) {
            return $this->redirect('/hr/payroll/periods')->withError('فترة الرواتب غير موجودة');
        }

        if ($period->status !== 'paid') {
            return $this->redirect('/hr/payroll/periods/' . $id)->withError('يجب صرف الرواتب أولاً قبل إغلاق الفترة');
        }

        $db = App::getInstance()->db();
        $db->update('hr_payroll_periods', [
            'status'     => 'closed',
            'updated_at' => date('Y-m-d H:i:s'),
        ], '`id` = ?', [(int) $id]);

        Logger::info("Payroll period closed", ['period_id' => (int) $id]);

        return $this->redirect('/hr/payroll/periods/' . $id)->withSuccess('تم إغلاق فترة الرواتب');
    }

    public function showRun(Request $request, string $id): Response
    {
        $db = App::getInstance()->db();

        $run = $db->selectOne("SELECT * FROM hr_payroll_runs WHERE id = ? AND is_archived = 0", [(int) $id]);
        if (!$run) {
            return $this->redirect('/hr/payroll/periods')->withError('سجل الراتب غير موجود');
        }

        $profile = HrEmployeeProfile::find((int) $run['employee_profile_id']);
        $period = HrPayrollPeriod::find((int) $run['period_id']);
        $components = HrPayrollRun::getComponentsLog((int) $id);

        $earnings = [];
        $deductions = [];
        foreach ($components as $comp) {
            if ($comp['type'] === 'earning') {
                $earnings[] = $comp;
            } else {
                $deductions[] = $comp;
            }
        }

        // Insurance record
        $insurance = $db->selectOne(
            "SELECT * FROM hr_insurance_records WHERE payroll_run_id = ? AND is_archived = 0",
            [(int) $id]
        );

        // Tax record
        $tax = $db->selectOne(
            "SELECT * FROM hr_tax_records WHERE payroll_run_id = ? AND is_archived = 0",
            [(int) $id]
        );

        return $this->view('HR.Views.payroll.show_run', [
            'run'        => $run,
            'profile'    => $profile,
            'period'     => $period,
            'earnings'   => $earnings,
            'deductions' => $deductions,
            'insurance'  => $insurance,
            'tax'        => $tax,
        ]);
    }

    public function salarySlip(Request $request, string $id): Response
    {
        $slipData = SalarySlipService::generateSlipData((int) $id);
        if (!$slipData) {
            return $this->redirect('/hr/payroll/periods')->withError('لا يمكن إنشاء قسيمة الراتب');
        }

        return $this->view('HR.Views.payroll.salary_slip', $slipData);
    }

    public function mySlips(Request $request): Response
    {
        $employee = $this->currentEmployee();
        if (!$employee) {
            return $this->redirect('/hr/payroll')->withError('غير مصرح');
        }

        $db = App::getInstance()->db();

        // Find HR profile for current employee
        $profile = $db->selectOne(
            "SELECT id FROM hr_employee_profiles WHERE employee_id = ? AND is_archived = 0",
            [(int) $employee->id]
        );

        if (!$profile) {
            return $this->redirect('/hr/payroll')->withError('لا يوجد ملف HR مرتبط بحسابك');
        }

        $runs = $db->select(
            "SELECT pr.*, pp.period_code, pp.year, pp.month
             FROM hr_payroll_runs pr
             JOIN hr_payroll_periods pp ON pp.id = pr.period_id
             WHERE pr.employee_profile_id = ? AND pr.is_archived = 0 AND pp.status IN ('paid','closed')
             ORDER BY pp.year DESC, pp.month DESC",
            [(int) $profile['id']]
        );

        return $this->view('HR.Views.payroll.my_slips', [
            'runs' => $runs,
        ]);
    }
}
