<?php
declare(strict_types=1);

namespace App\Modules\Accounting\Services;

use App\Core\App;
use App\Core\EventBus;
use App\Core\Logger;
use App\Modules\Accounting\Models\FiscalYear;
use App\Modules\Accounting\Models\PeriodClosing;

/**
 * Handles monthly and yearly period closing.
 * - Monthly: locks the period, no more postings allowed
 * - Yearly: closes revenue/expense to retained earnings, carries balances forward
 */
final class PeriodClosingService
{
    /**
     * Close a monthly period.
     */
    public static function closeMonth(int $fiscalYearId, string $period): array
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $fiscalYear = FiscalYear::find($fiscalYearId);
        if (!$fiscalYear || !$fiscalYear->isOpen()) {
            return ['success' => false, 'error' => 'السنة المالية غير مفتوحة'];
        }

        // Check if already closed
        if (PeriodClosing::isPeriodClosed($fiscalYearId, $period)) {
            return ['success' => false, 'error' => 'الفترة مغلقة بالفعل'];
        }

        // Check for unposted draft entries in this period
        $dateFrom = $period . '-01';
        $dateTo = date('Y-m-t', strtotime($dateFrom));
        $drafts = $db->selectOne(
            "SELECT COUNT(*) as cnt FROM journal_entries
             WHERE fiscal_year_id = ? AND entry_date >= ? AND entry_date <= ?
               AND status = 'draft' AND is_archived = 0",
            [$fiscalYearId, $dateFrom, $dateTo]
        );
        if ((int) ($drafts['cnt'] ?? 0) > 0) {
            return ['success' => false, 'error' => 'يوجد ' . $drafts['cnt'] . ' قيود مسودة غير مرحّلة — يجب ترحيلها أو حذفها أولاً'];
        }

        // Get period totals
        $totals = $db->selectOne(
            "SELECT COALESCE(SUM(total_debit), 0) as total_debit,
                    COALESCE(SUM(total_credit), 0) as total_credit,
                    COUNT(*) as journal_count
             FROM journal_entries
             WHERE fiscal_year_id = ? AND entry_date >= ? AND entry_date <= ?
               AND status = 'posted' AND is_archived = 0",
            [$fiscalYearId, $dateFrom, $dateTo]
        );

        $db->beginTransaction();
        try {
            // Create or update period closing record
            $existing = PeriodClosing::findByPeriod($fiscalYearId, $period);
            if ($existing) {
                $db->update('period_closings', [
                    'status'        => 'closed',
                    'total_debit'   => $totals['total_debit'],
                    'total_credit'  => $totals['total_credit'],
                    'journal_count' => (int) $totals['journal_count'],
                    'closed_at'     => date('Y-m-d H:i:s'),
                    'closed_by'     => $employee ? (int) $employee->id : null,
                    'updated_at'    => date('Y-m-d H:i:s'),
                ], '`id` = ?', [(int) $existing->id]);
            } else {
                $db->insert('period_closings', [
                    'fiscal_year_id' => $fiscalYearId,
                    'period'         => $period,
                    'period_start'   => $dateFrom,
                    'period_end'     => $dateTo,
                    'closing_type'   => 'monthly',
                    'status'         => 'closed',
                    'total_debit'    => $totals['total_debit'],
                    'total_credit'   => $totals['total_credit'],
                    'journal_count'  => (int) $totals['journal_count'],
                    'closed_at'      => date('Y-m-d H:i:s'),
                    'closed_by'      => $employee ? (int) $employee->id : null,
                    '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,
                ]);
            }

            // Lock journal entries in this period
            $db->execute(
                "UPDATE journal_entries SET period_closed = 1
                 WHERE fiscal_year_id = ? AND entry_date >= ? AND entry_date <= ?
                   AND status = 'posted' AND is_archived = 0",
                [$fiscalYearId, $dateFrom, $dateTo]
            );

            $db->commit();

            EventBus::dispatch('accounting.period.closed', [
                'fiscal_year_id' => $fiscalYearId,
                'period'         => $period,
            ]);

            Logger::info("Period closed", ['fiscal_year_id' => $fiscalYearId, 'period' => $period]);

            return ['success' => true];
        } catch (\Throwable $e) {
            $db->rollBack();
            return ['success' => false, 'error' => 'فشل إغلاق الفترة: ' . $e->getMessage()];
        }
    }

    /**
     * Reopen a closed monthly period (requires special permission).
     */
    public static function reopenMonth(int $fiscalYearId, string $period, string $reason): array
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $pc = PeriodClosing::findByPeriod($fiscalYearId, $period);
        if (!$pc || $pc->status !== 'closed') {
            return ['success' => false, 'error' => 'الفترة غير مغلقة'];
        }

        $db->beginTransaction();
        try {
            $db->update('period_closings', [
                'status'        => 'reopened',
                'reopened_at'   => date('Y-m-d H:i:s'),
                'reopened_by'   => $employee ? (int) $employee->id : null,
                'reopen_reason' => $reason,
                'updated_at'    => date('Y-m-d H:i:s'),
            ], '`id` = ?', [(int) $pc->id]);

            // Unlock journal entries
            $dateFrom = $period . '-01';
            $dateTo = date('Y-m-t', strtotime($dateFrom));
            $db->execute(
                "UPDATE journal_entries SET period_closed = 0
                 WHERE fiscal_year_id = ? AND entry_date >= ? AND entry_date <= ?
                   AND status = 'posted' AND is_archived = 0",
                [$fiscalYearId, $dateFrom, $dateTo]
            );

            $db->commit();

            Logger::info("Period reopened", [
                'fiscal_year_id' => $fiscalYearId,
                'period' => $period,
                'reason' => $reason,
            ]);

            return ['success' => true];
        } catch (\Throwable $e) {
            $db->rollBack();
            return ['success' => false, 'error' => 'فشل إعادة فتح الفترة: ' . $e->getMessage()];
        }
    }

    /**
     * Close fiscal year — creates closing entry moving revenue/expense to retained earnings.
     */
    public static function closeFiscalYear(int $fiscalYearId): array
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $fiscalYear = FiscalYear::find($fiscalYearId);
        if (!$fiscalYear) {
            return ['success' => false, 'error' => 'السنة المالية غير موجودة'];
        }
        if (!$fiscalYear->isOpen()) {
            return ['success' => false, 'error' => 'السنة المالية مغلقة بالفعل'];
        }

        // Check all monthly periods are closed
        $startDate = $fiscalYear->start_date;
        $endDate = $fiscalYear->end_date;
        $start = new \DateTime($startDate);
        $end = new \DateTime($endDate);
        $unclosedPeriods = [];
        while ($start <= $end) {
            $period = $start->format('Y-m');
            if (!PeriodClosing::isPeriodClosed($fiscalYearId, $period)) {
                $unclosedPeriods[] = $period;
            }
            $start->modify('first day of next month');
        }
        if (!empty($unclosedPeriods)) {
            return [
                'success' => false,
                'error' => 'يوجد فترات غير مغلقة: ' . implode(', ', $unclosedPeriods) . ' — يجب إغلاقها أولاً',
            ];
        }

        // Get income statement for the entire year
        $incomeStatement = FinancialReportService::getIncomeStatement(
            $fiscalYear->start_date,
            $fiscalYear->end_date
        );

        $netIncome = $incomeStatement['net_income'];

        // Retained earnings account (3102)
        $retainedEarnings = $db->selectOne(
            "SELECT id FROM chart_of_accounts WHERE account_code = '3102' AND is_archived = 0"
        );
        if (!$retainedEarnings) {
            return ['success' => false, 'error' => 'حساب الأرباح المحتجزة (3102) غير موجود'];
        }

        $lines = [];

        // Close revenue accounts (debit revenue accounts to zero them out)
        foreach ($incomeStatement['revenue'] as $rev) {
            if (bccomp((string) $rev['balance'], '0.00', 2) !== 0) {
                $lines[] = [
                    'account_id'     => (int) $rev['id'],
                    'debit'          => (string) $rev['balance'],
                    'credit'         => '0.00',
                    'description_ar' => 'إقفال حساب إيرادات — ' . $rev['name_ar'],
                ];
            }
        }

        // Close expense accounts (credit expense accounts to zero them out)
        foreach ($incomeStatement['expenses'] as $exp) {
            if (bccomp((string) $exp['balance'], '0.00', 2) !== 0) {
                $lines[] = [
                    'account_id'     => (int) $exp['id'],
                    'debit'          => '0.00',
                    'credit'         => (string) $exp['balance'],
                    'description_ar' => 'إقفال حساب مصروفات — ' . $exp['name_ar'],
                ];
            }
        }

        // Net income to retained earnings
        if (bccomp($netIncome, '0.00', 2) > 0) {
            // Profit: Cr. Retained Earnings
            $lines[] = [
                'account_id'     => (int) $retainedEarnings['id'],
                'debit'          => '0.00',
                'credit'         => $netIncome,
                'description_ar' => 'صافي ربح السنة المالية — أرباح محتجزة',
            ];
        } elseif (bccomp($netIncome, '0.00', 2) < 0) {
            // Loss: Dr. Retained Earnings
            $lines[] = [
                'account_id'     => (int) $retainedEarnings['id'],
                'debit'          => bcmul($netIncome, '-1', 2),
                'credit'         => '0.00',
                'description_ar' => 'صافي خسارة السنة المالية — أرباح محتجزة',
            ];
        }

        if (empty($lines)) {
            return ['success' => false, 'error' => 'لا توجد حسابات إيرادات أو مصروفات لإقفالها'];
        }

        // Step 1: Create closing journal entry (JournalService manages its own transaction)
        $closingResult = JournalService::createEntry([
            'entry_date'        => $fiscalYear->end_date,
            'description_ar'    => 'قيد إقفال السنة المالية ' . $fiscalYear->name_ar,
            'description_en'    => 'Year-end closing entry — ' . $fiscalYear->name_en,
            'reference_type'    => 'closing',
            'source_module'     => 'accounting',
            'is_auto_generated' => 1,
        ], $lines, true);

        if (!$closingResult['success']) {
            return $closingResult;
        }

        // Step 2: Update fiscal year status and create year-end period record
        $db->beginTransaction();
        try {
            $db->update('fiscal_years', [
                'status'     => 'closed',
                'is_current' => 0,
                'closed_at'  => date('Y-m-d H:i:s'),
                'closed_by'  => $employee ? (int) $employee->id : null,
                'updated_at' => date('Y-m-d H:i:s'),
            ], '`id` = ?', [$fiscalYearId]);

            $endPeriod = substr($fiscalYear->end_date, 0, 7);
            $db->insert('period_closings', [
                'fiscal_year_id'  => $fiscalYearId,
                'period'          => $endPeriod . '-YE',
                'period_start'    => $fiscalYear->start_date,
                'period_end'      => $fiscalYear->end_date,
                'closing_type'    => 'yearly',
                'status'          => 'closed',
                'closing_entry_id' => $closingResult['journal_entry_id'],
                'closed_at'       => date('Y-m-d H:i:s'),
                'closed_by'       => $employee ? (int) $employee->id : null,
                '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,
            ]);

            $db->commit();
        } catch (\Throwable $e) {
            $db->rollBack();
            Logger::error("Fiscal year status update failed (closing entry already posted): " . $e->getMessage());
            return ['success' => false, 'error' => 'تم ترحيل قيد الإقفال لكن فشل تحديث حالة السنة المالية: ' . $e->getMessage()];
        }

        EventBus::dispatch('accounting.fiscal_year.closed', [
            'fiscal_year_id' => $fiscalYearId,
            'net_income'     => $netIncome,
            'closing_entry'  => $closingResult['journal_entry_id'],
        ]);

        Logger::info("Fiscal year closed", [
            'fiscal_year_id' => $fiscalYearId,
            'net_income'     => $netIncome,
        ]);

        return [
            'success'           => true,
            'net_income'        => $netIncome,
            'closing_entry_id'  => $closingResult['journal_entry_id'],
            'closing_entry_num' => $closingResult['entry_number'],
        ];
    }

    /**
     * Get period status summary for a fiscal year (calendar view).
     */
    public static function getPeriodSummary(int $fiscalYearId): array
    {
        $db = App::getInstance()->db();

        $fiscalYear = FiscalYear::find($fiscalYearId);
        if (!$fiscalYear) {
            return [];
        }

        $periods = [];
        $start = new \DateTime($fiscalYear->start_date);
        $end = new \DateTime($fiscalYear->end_date);

        while ($start <= $end) {
            $period = $start->format('Y-m');
            $periodStart = $start->format('Y-m-01');
            $periodEnd = $start->format('Y-m-t');

            $pc = PeriodClosing::findByPeriod($fiscalYearId, $period);

            $totals = $db->selectOne(
                "SELECT COALESCE(SUM(total_debit), 0) as total_debit,
                        COUNT(*) as journal_count
                 FROM journal_entries
                 WHERE fiscal_year_id = ? AND entry_date >= ? AND entry_date <= ?
                   AND status = 'posted' AND is_archived = 0",
                [$fiscalYearId, $periodStart, $periodEnd]
            );

            $periods[] = [
                'period'        => $period,
                'period_start'  => $periodStart,
                'period_end'    => $periodEnd,
                'status'        => $pc ? $pc->status : 'open',
                'total_debit'   => $totals['total_debit'] ?? '0.00',
                'journal_count' => (int) ($totals['journal_count'] ?? 0),
                'closed_at'     => $pc ? $pc->closed_at : null,
            ];

            $start->modify('first day of next month');
        }

        return $periods;
    }
}
