<?php

namespace App\Services;

use App\Models\AuditLog;
use App\Models\FiscalYear;
use App\Models\Setting;
use Carbon\Carbon;
use DateTimeInterface;
use Illuminate\Support\Facades\Hash;
use RuntimeException;

class BooksClosingService
{
    public function closingDate(int $companyId): ?Carbon
    {
        $value = trim((string) Setting::get($companyId, 'books_closing_date', ''));

        return $value !== '' ? Carbon::parse($value)->startOfDay() : null;
    }

    public function hasClosingPassword(int $companyId): bool
    {
        return trim((string) Setting::get($companyId, 'books_closing_password', '')) !== '';
    }

    public function isClosed(int $companyId, DateTimeInterface|string $date): bool
    {
        $closing = $this->closingDate($companyId);

        if (!$closing) {
            return false;
        }

        return Carbon::parse($date)->startOfDay()->lte($closing);
    }

    /**
     * QuickBooks-style guard: block transactions on or before the books closing date.
     *
     * @throws RuntimeException
     */
    public function assertCanTransact(int $companyId, DateTimeInterface|string $date, ?string $password = null): void
    {
        if (!$this->isClosed($companyId, $date)) {
            return;
        }

        if ($password !== null && $password !== '' && $this->verifyPassword($companyId, $password)) {
            return;
        }

        $closing = $this->closingDate($companyId);
        $formattedDate = Carbon::parse($date)->format('d M Y');

        throw new RuntimeException(
            "Transactions dated on or before {$closing->format('d M Y')} are closed. " .
            "The date {$formattedDate} falls in a closed period. " .
            ($this->hasClosingPassword($companyId)
                ? 'Enter the closing password or update the closing date under Settings → Period Closing.'
                : 'Update the closing date under Settings → Period Closing to make changes.')
        );
    }

    public function verifyPassword(int $companyId, string $password): bool
    {
        $hash = (string) Setting::get($companyId, 'books_closing_password', '');

        return $hash !== '' && Hash::check($password, $hash);
    }

    /**
     * Set or clear the books closing date (QuickBooks "Close the Books").
     *
     * @param  array{date?: ?string, password?: ?string, remove_password?: bool}  $options
     */
    public function updateClosing(int $companyId, array $options, ?int $userId = null): void
    {
        $oldDate = Setting::get($companyId, 'books_closing_date', '');

        if (array_key_exists('date', $options)) {
            $date = trim((string) ($options['date'] ?? ''));

            if ($date === '') {
                Setting::set($companyId, 'books_closing_date', '');
            } else {
                Setting::set($companyId, 'books_closing_date', Carbon::parse($date)->toDateString());
                $this->lockPeriodsThrough($companyId, Carbon::parse($date), $userId);
            }
        }

        if (!empty($options['remove_password'])) {
            Setting::set($companyId, 'books_closing_password', '');
        } elseif (!empty($options['password'])) {
            Setting::set($companyId, 'books_closing_password', Hash::make($options['password']));
        }

        $newDate = Setting::get($companyId, 'books_closing_date', '');

        AuditLog::record(
            'update',
            'books_closing',
            (string) $companyId,
            ['books_closing_date' => $oldDate ?: null],
            ['books_closing_date' => $newDate ?: null],
            $newDate
                ? 'Books closing date set to ' . Carbon::parse($newDate)->format('d M Y')
                : 'Books closing date removed'
        );
    }

    /**
     * Suggest the last day of the most recently completed fiscal year.
     */
    public function suggestedClosingDate(int $companyId): ?Carbon
    {
        $fiscalYears = FiscalYear::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->orderByDesc('end_date')
            ->get();

        $completed = $fiscalYears->first(fn (FiscalYear $fy) => $fy->end_date->lt(now()->startOfDay()));

        return $completed?->end_date->copy();
    }

    private function lockPeriodsThrough(int $companyId, Carbon $closingDate, ?int $userId): void
    {
        $fiscalYears = FiscalYear::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('status', 'open')
            ->whereDate('end_date', '<=', $closingDate)
            ->get();

        foreach ($fiscalYears as $fiscalYear) {
            $fiscalYear->update([
                'status' => 'locked',
                'closed_at' => now(),
                'closed_by' => $userId,
            ]);

            AuditLog::record(
                'lock',
                'fiscal_year',
                (string) $fiscalYear->id,
                ['status' => 'open'],
                ['status' => 'locked', 'via' => 'books_closing'],
                "Fiscal year locked via books closing: {$fiscalYear->name}"
            );
        }

        $current = app(FiscalYearService::class)->resolveForDate($companyId, now(), false);
        if ($current) {
            FiscalYear::withoutGlobalScopes()
                ->where('company_id', $companyId)
                ->update(['is_current' => false]);

            $current->update(['is_current' => true]);
        }
    }
}
