<?php

declare(strict_types=1);

namespace App\Filament\Resources\DberpSettlementWriteOffs;

use App\Business\Settlement\Enums\SettlementWriteOffStatus;
use App\Filament\Concerns\InteractsWithDberpPermissions;
use App\Filament\Contracts\DberpFilamentCreateSaveFirstGuidance;
use App\Filament\Contracts\DberpFilamentFormEditability;
use App\Filament\Resources\DberpSettlementWriteOffs\Pages\CreateDberpSettlementWriteOff;
use App\Filament\Resources\DberpSettlementWriteOffs\Pages\EditDberpSettlementWriteOff;
use App\Filament\Resources\DberpSettlementWriteOffs\Pages\ListDberpSettlementWriteOffs;
use App\Filament\Resources\DberpSettlementWriteOffs\RelationManagers\LinesRelationManager;
use App\Filament\Support\DberpFilamentNumberFields;
use App\Filament\Support\DberpFilamentRelationshipSelectLimits;
use App\Models\DberpCustomer;
use App\Models\DberpEmployee;
use App\Models\DberpPayableRecord;
use App\Models\DberpPaymentOrder;
use App\Models\DberpReceiptOrder;
use App\Models\DberpReceivableRecord;
use App\Models\DberpSettlementWriteOff;
use App\Models\DberpSupplier;
use App\Services\DberpPermissionService;
use App\Support\DberpFilamentSearchableSelectOptions;
use App\Support\DberpNumberFormatter;
use App\Support\DberpProductSkuSelectOptions;
use App\Support\DberpSettlementWriteOffCounterpartyNames;
use BackedEnum;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use UnitEnum;

class DberpSettlementWriteOffResource extends Resource implements DberpFilamentCreateSaveFirstGuidance, DberpFilamentFormEditability
{
    use InteractsWithDberpPermissions;

    protected static ?string $model = DberpSettlementWriteOff::class;

    protected static ?string $recordTitleAttribute = 'code';

    protected static ?int $navigationSort = 60;

    public static function isRecordFormEditableForFilament(Model $record): bool
    {
        return false;
    }

    public static function createSaveFirstGuidanceTranslationKey(): ?string
    {
        return 'dberp.messages.save_document_first_then_manage_verification_lines';
    }

    public static function form(Schema $schema): Schema
    {
        return $schema
            ->components([
                Section::make(__('dberp.sections.settlement_write_off'))
                    ->schema([
                        TextInput::make('code')
                            ->label(__('dberp.fields.write_off_code'))
                            ->disabled()
                            ->dehydrated(false)
                            ->visible(fn (?DberpSettlementWriteOff $record): bool => $record !== null),
                        Select::make('settlement_side')
                            ->label(__('dberp.fields.settlement_side'))
                            ->options(__('dberp.options.settlement_side'))
                            ->default('receivable')
                            ->required()
                            ->disabled(fn (?DberpSettlementWriteOff $record): bool => $record !== null),
                        Select::make('counterparty_id')
                            ->label(__('dberp.fields.counterparty'))
                            ->options(function (callable $get): array {
                                return $get('settlement_side') === 'payable'
                                    ? DberpSupplier::query()->where('is_enabled', true)->orderBy('sort_order')->orderBy('id')->pluck('name', 'id')->all()
                                    : DberpCustomer::query()->where('is_enabled', true)->orderBy('sort_order')->orderBy('id')->pluck('name', 'id')->all();
                            })
                            ->searchable()
                            ->preload()
                            ->required()
                            ->disabled(fn (?DberpSettlementWriteOff $record): bool => $record !== null),
                        Select::make('handler_employee_id')
                            ->label(__('dberp.fields.handler_employee'))
                            ->options(fn (): array => DberpEmployee::selectOptionsForFilament(fn (Builder $q) => $q->where('is_enabled', true)))
                            ->searchable()
                            ->preload()
                            ->disabled(fn (?DberpSettlementWriteOff $record): bool => $record !== null),
                        DateTimePicker::make('business_date')
                            ->label(__('dberp.fields.business_time'))
                            ->default(now())
                            ->required()
                            ->seconds(false)
                            ->disabled(fn (?DberpSettlementWriteOff $record): bool => $record !== null),
                        DberpFilamentNumberFields::displayMoney(
                            TextInput::make('total_amount')
                                ->label(__('dberp.fields.total_amount'))
                                ->disabled()
                                ->dehydrated(false)
                                ->visible(fn (?DberpSettlementWriteOff $record): bool => $record !== null),
                        ),
                        DberpFilamentNumberFields::displayMoney(
                            TextInput::make('effective_amount')
                                ->label(__('dberp.fields.effective_amount'))
                                ->disabled()
                                ->dehydrated(false)
                                ->visible(fn (?DberpSettlementWriteOff $record): bool => $record !== null),
                        ),
                        Textarea::make('remark')
                            ->label(__('dberp.fields.remark'))
                            ->rows(2)
                            ->columnSpanFull()
                            ->disabled(fn (?DberpSettlementWriteOff $record): bool => $record !== null),
                        Repeater::make('lines')
                            ->label(__('dberp.relations.write_off_lines'))
                            ->columnSpanFull()
                            ->defaultItems(1)
                            ->minItems(1)
                            ->dehydrated(fn (?DberpSettlementWriteOff $record): bool => $record === null)
                            ->disabled(fn (?DberpSettlementWriteOff $record): bool => $record !== null)
                            ->schema([
                                DberpFilamentSearchableSelectOptions::configure(
                                    Select::make('receipt_order_id')
                                        ->label(__('dberp.fields.receipt_code'))
                                        ->visible(fn (callable $get): bool => $get('../../settlement_side') === 'receivable')
                                        ->required(fn (callable $get): bool => $get('../../settlement_side') === 'receivable'),
                                    fn (): int => static::writeOffReceiptOrderCount(),
                                    fn (): array => static::searchReceiptOrderOptions(''),
                                    fn (string $search): array => static::searchReceiptOrderOptions($search),
                                    fn ($value): ?string => static::receiptOrderOptionLabel($value),
                                ),
                                DberpFilamentSearchableSelectOptions::configure(
                                    Select::make('receivable_record_id')
                                        ->label(__('dberp.fields.receivable_record_code'))
                                        ->visible(fn (callable $get): bool => $get('../../settlement_side') === 'receivable')
                                        ->required(fn (callable $get): bool => $get('../../settlement_side') === 'receivable'),
                                    fn (): int => static::writeOffReceivableRecordCount(),
                                    fn (): array => static::searchReceivableRecordOptions(''),
                                    fn (string $search): array => static::searchReceivableRecordOptions($search),
                                    fn ($value): ?string => static::receivableRecordOptionLabel($value),
                                ),
                                DberpFilamentSearchableSelectOptions::configure(
                                    Select::make('payment_order_id')
                                        ->label(__('dberp.fields.payment_code'))
                                        ->visible(fn (callable $get): bool => $get('../../settlement_side') === 'payable')
                                        ->required(fn (callable $get): bool => $get('../../settlement_side') === 'payable'),
                                    fn (): int => static::writeOffPaymentOrderCount(),
                                    fn (): array => static::searchPaymentOrderOptions(''),
                                    fn (string $search): array => static::searchPaymentOrderOptions($search),
                                    fn ($value): ?string => static::paymentOrderOptionLabel($value),
                                ),
                                DberpFilamentSearchableSelectOptions::configure(
                                    Select::make('payable_record_id')
                                        ->label(__('dberp.fields.payable_record_code'))
                                        ->visible(fn (callable $get): bool => $get('../../settlement_side') === 'payable')
                                        ->required(fn (callable $get): bool => $get('../../settlement_side') === 'payable'),
                                    fn (): int => static::writeOffPayableRecordCount(),
                                    fn (): array => static::searchPayableRecordOptions(''),
                                    fn (string $search): array => static::searchPayableRecordOptions($search),
                                    fn ($value): ?string => static::payableRecordOptionLabel($value),
                                ),
                                DberpFilamentNumberFields::moneyInput(
                                    TextInput::make('write_off_amount')
                                        ->label(__('dberp.fields.write_off_amount'))
                                        ->required(),
                                    withHelperText: false,
                                )
                                    ->formatStateUsing(fn ($state, ?DberpSettlementWriteOff $record): mixed => $record !== null
                                        ? DberpNumberFormatter::money($state)
                                        : $state),
                                Textarea::make('remark')
                                    ->label(__('dberp.fields.remark'))
                                    ->rows(2)
                                    ->columnSpanFull(),
                            ])
                            ->columns(2),
                    ])
                    ->columns(2),
            ])->columns(1);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->defaultSort('business_date', 'desc')
            ->columns([
                TextColumn::make('code')
                    ->label(__('dberp.fields.write_off_code'))
                    ->searchable(),
                TextColumn::make('settlement_side')
                    ->label(__('dberp.fields.settlement_side'))
                    ->badge()
                    ->formatStateUsing(fn (?string $state): string => __('dberp.options.settlement_side')[$state] ?? (string) $state),
                TextColumn::make('counterparty_id')
                    ->label(__('dberp.fields.counterparty'))
                    ->formatStateUsing(fn (?string $state, DberpSettlementWriteOff $record): string => app(DberpSettlementWriteOffCounterpartyNames::class)->name($record)),
                TextColumn::make('total_amount')
                    ->label(__('dberp.fields.total_amount'))
                    ->formatStateUsing(fn ($state): string => DberpNumberFormatter::money($state)),
                TextColumn::make('effective_amount')
                    ->label(__('dberp.fields.effective_amount'))
                    ->formatStateUsing(fn ($state): string => DberpNumberFormatter::money($state)),
                TextColumn::make('write_off_status')
                    ->label(__('dberp.fields.status'))
                    ->badge()
                    ->formatStateUsing(fn (?string $state): string => SettlementWriteOffStatus::tryFrom((string) $state)?->label() ?? (string) $state),
                TextColumn::make('handlerEmployee.name')
                    ->label(__('dberp.fields.handler_employee'))
                    ->toggleable(isToggledHiddenByDefault: true),
                TextColumn::make('business_date')
                    ->label(__('dberp.fields.business_time'))
                    ->dateTime(),
                TextColumn::make('confirmed_at')
                    ->label(__('dberp.fields.confirmed_at'))
                    ->dateTime()
                    ->toggleable(isToggledHiddenByDefault: true),
            ])
            ->filters([
                SelectFilter::make('settlement_side')
                    ->label(__('dberp.fields.settlement_side'))
                    ->options(__('dberp.options.settlement_side')),
                SelectFilter::make('write_off_status')
                    ->label(__('dberp.fields.status'))
                    ->options(SettlementWriteOffStatus::options()),
                SelectFilter::make('handler_employee_id')
                    ->label(__('dberp.fields.handler_employee'))
                    ->options(fn (): array => DberpEmployee::selectOptionsForFilament())
                    ->searchable()
                    ->preload(),
                SelectFilter::make('attention_status')
                    ->label(__('dberp.fields.attention_status'))
                    ->options(__('dberp.options.settlement_attention_status_write_off'))
                    ->query(function (Builder $query, array $data): Builder {
                        return ($data['value'] ?? null) === 'reversed_follow_up'
                            ? $query
                                ->whereIn('write_off_status', [SettlementWriteOffStatus::PartiallyReversed->value, SettlementWriteOffStatus::Reversed->value])
                                ->where(function (Builder $builder): void {
                                    $builder->whereDate('updated_at', '>=', now()->subDays((int) config('dberp.settlement.reminders.follow_up_days', 7))->toDateString())
                                        ->orWhereDate('confirmed_at', '>=', now()->subDays((int) config('dberp.settlement.reminders.follow_up_days', 7))->toDateString());
                                })
                            : $query;
                    }),
            ]);
    }

    public static function getEloquentQuery(): Builder
    {
        return parent::getEloquentQuery()->with(['handlerEmployee']);
    }

    public static function canViewAny(): bool
    {
        return static::allowsAnyPermission([
            DberpPermissionService::VIEW_SETTLEMENT_WRITE_OFF,
            DberpPermissionService::WRITE_OFF,
            DberpPermissionService::REVERSE_WRITE_OFF,
        ]);
    }

    public static function canCreate(): bool
    {
        return static::allowsPermission(DberpPermissionService::WRITE_OFF);
    }

    public static function canEdit(Model $record): bool
    {
        return static::allowsAnyPermission([
            DberpPermissionService::WRITE_OFF,
            DberpPermissionService::REVERSE_WRITE_OFF,
        ]);
    }

    public static function getRelations(): array
    {
        return [
            LinesRelationManager::class,
        ];
    }

    public static function getPages(): array
    {
        return [
            'index' => ListDberpSettlementWriteOffs::route('/'),
            'create' => CreateDberpSettlementWriteOff::route('/create'),
            'edit' => EditDberpSettlementWriteOff::route('/{record}/edit'),
        ];
    }

    public static function getNavigationGroup(): string|UnitEnum|null
    {
        return __('dberp.nav.settlement');
    }

    public static function getNavigationIcon(): string|BackedEnum|Htmlable|null
    {
        return null;
    }

    public static function getModelLabel(): string
    {
        return __('dberp.resources.settlement_write_off.label');
    }

    public static function getPluralModelLabel(): string
    {
        return __('dberp.resources.settlement_write_off.plural');
    }

    private static function writeOffReceiptOrderCount(): int
    {
        return static::writeOffReceiptOrderQuery()->count();
    }

    /**
     * @return Builder<DberpReceiptOrder>
     */
    private static function writeOffReceiptOrderQuery(): Builder
    {
        return DberpReceiptOrder::query()
            ->with('customer')
            ->where('status', 'effective')
            ->where('available_amount', '>', 0);
    }

    /**
     * @return array<int|string, string>
     */
    private static function searchReceiptOrderOptions(string $search): array
    {
        $search = trim($search);
        $query = static::writeOffReceiptOrderQuery();

        if ($search !== '') {
            $query->where(function (Builder $builder) use ($search): void {
                $builder->where('code', 'like', '%'.$search.'%')
                    ->orWhereHas('customer', fn (Builder $customerQuery): Builder => $customerQuery->where('name', 'like', '%'.$search.'%'));
            });
        }

        $query->orderByDesc('business_time');

        if (static::writeOffReceiptOrderCount() >= DberpProductSkuSelectOptions::SEARCHABLE_OPTION_THRESHOLD) {
            $query->limit(DberpFilamentRelationshipSelectLimits::PRELOAD_OPTION_LIMIT);
        }

        return $query
            ->get()
            ->mapWithKeys(fn (DberpReceiptOrder $item): array => [
                $item->getKey() => static::formatReceiptOrderOption($item),
            ])
            ->all();
    }

    private static function receiptOrderOptionLabel(mixed $value): ?string
    {
        if ($value === null || $value === '') {
            return null;
        }

        $item = DberpReceiptOrder::query()->with('customer')->find((int) $value);

        return $item instanceof DberpReceiptOrder ? static::formatReceiptOrderOption($item) : null;
    }

    private static function formatReceiptOrderOption(DberpReceiptOrder $item): string
    {
        return sprintf(
            '%s / %s / %s',
            $item->code,
            $item->customer?->name ?? '-',
            DberpNumberFormatter::money((float) $item->available_amount),
        );
    }

    private static function writeOffReceivableRecordCount(): int
    {
        return static::writeOffReceivableRecordQuery()->count();
    }

    /**
     * @return Builder<DberpReceivableRecord>
     */
    private static function writeOffReceivableRecordQuery(): Builder
    {
        return DberpReceivableRecord::query()
            ->with('customer')
            ->where('remaining_amount', '>', 0)
            ->whereNot('record_status', 'voided');
    }

    /**
     * @return array<int|string, string>
     */
    private static function searchReceivableRecordOptions(string $search): array
    {
        $search = trim($search);
        $query = static::writeOffReceivableRecordQuery();

        if ($search !== '') {
            $query->where(function (Builder $builder) use ($search): void {
                $builder->where('code', 'like', '%'.$search.'%')
                    ->orWhereHas('customer', fn (Builder $customerQuery): Builder => $customerQuery->where('name', 'like', '%'.$search.'%'));
            });
        }

        $query->orderByDesc('business_date');

        if (static::writeOffReceivableRecordCount() >= DberpProductSkuSelectOptions::SEARCHABLE_OPTION_THRESHOLD) {
            $query->limit(DberpFilamentRelationshipSelectLimits::PRELOAD_OPTION_LIMIT);
        }

        return $query
            ->get()
            ->mapWithKeys(fn (DberpReceivableRecord $item): array => [
                $item->getKey() => static::formatReceivableRecordOption($item),
            ])
            ->all();
    }

    private static function receivableRecordOptionLabel(mixed $value): ?string
    {
        if ($value === null || $value === '') {
            return null;
        }

        $item = DberpReceivableRecord::query()->with('customer')->find((int) $value);

        return $item instanceof DberpReceivableRecord ? static::formatReceivableRecordOption($item) : null;
    }

    private static function formatReceivableRecordOption(DberpReceivableRecord $item): string
    {
        return sprintf(
            '%s / %s / %s',
            $item->code,
            $item->customer?->name ?? '-',
            DberpNumberFormatter::money((float) $item->remaining_amount),
        );
    }

    private static function writeOffPaymentOrderCount(): int
    {
        return static::writeOffPaymentOrderQuery()->count();
    }

    /**
     * @return Builder<DberpPaymentOrder>
     */
    private static function writeOffPaymentOrderQuery(): Builder
    {
        return DberpPaymentOrder::query()
            ->with('supplier')
            ->where('status', 'effective')
            ->where('available_amount', '>', 0);
    }

    /**
     * @return array<int|string, string>
     */
    private static function searchPaymentOrderOptions(string $search): array
    {
        $search = trim($search);
        $query = static::writeOffPaymentOrderQuery();

        if ($search !== '') {
            $query->where(function (Builder $builder) use ($search): void {
                $builder->where('code', 'like', '%'.$search.'%')
                    ->orWhereHas('supplier', fn (Builder $supplierQuery): Builder => $supplierQuery->where('name', 'like', '%'.$search.'%'));
            });
        }

        $query->orderByDesc('business_time');

        if (static::writeOffPaymentOrderCount() >= DberpProductSkuSelectOptions::SEARCHABLE_OPTION_THRESHOLD) {
            $query->limit(DberpFilamentRelationshipSelectLimits::PRELOAD_OPTION_LIMIT);
        }

        return $query
            ->get()
            ->mapWithKeys(fn (DberpPaymentOrder $item): array => [
                $item->getKey() => static::formatPaymentOrderOption($item),
            ])
            ->all();
    }

    private static function paymentOrderOptionLabel(mixed $value): ?string
    {
        if ($value === null || $value === '') {
            return null;
        }

        $item = DberpPaymentOrder::query()->with('supplier')->find((int) $value);

        return $item instanceof DberpPaymentOrder ? static::formatPaymentOrderOption($item) : null;
    }

    private static function formatPaymentOrderOption(DberpPaymentOrder $item): string
    {
        return sprintf(
            '%s / %s / %s',
            $item->code,
            $item->supplier?->name ?? '-',
            DberpNumberFormatter::money((float) $item->available_amount),
        );
    }

    private static function writeOffPayableRecordCount(): int
    {
        return static::writeOffPayableRecordQuery()->count();
    }

    /**
     * @return Builder<DberpPayableRecord>
     */
    private static function writeOffPayableRecordQuery(): Builder
    {
        return DberpPayableRecord::query()
            ->with('supplier')
            ->where('remaining_amount', '>', 0)
            ->whereNot('record_status', 'voided');
    }

    /**
     * @return array<int|string, string>
     */
    private static function searchPayableRecordOptions(string $search): array
    {
        $search = trim($search);
        $query = static::writeOffPayableRecordQuery();

        if ($search !== '') {
            $query->where(function (Builder $builder) use ($search): void {
                $builder->where('code', 'like', '%'.$search.'%')
                    ->orWhereHas('supplier', fn (Builder $supplierQuery): Builder => $supplierQuery->where('name', 'like', '%'.$search.'%'));
            });
        }

        $query->orderByDesc('business_date');

        if (static::writeOffPayableRecordCount() >= DberpProductSkuSelectOptions::SEARCHABLE_OPTION_THRESHOLD) {
            $query->limit(DberpFilamentRelationshipSelectLimits::PRELOAD_OPTION_LIMIT);
        }

        return $query
            ->get()
            ->mapWithKeys(fn (DberpPayableRecord $item): array => [
                $item->getKey() => static::formatPayableRecordOption($item),
            ])
            ->all();
    }

    private static function payableRecordOptionLabel(mixed $value): ?string
    {
        if ($value === null || $value === '') {
            return null;
        }

        $item = DberpPayableRecord::query()->with('supplier')->find((int) $value);

        return $item instanceof DberpPayableRecord ? static::formatPayableRecordOption($item) : null;
    }

    private static function formatPayableRecordOption(DberpPayableRecord $item): string
    {
        return sprintf(
            '%s / %s / %s',
            $item->code,
            $item->supplier?->name ?? '-',
            DberpNumberFormatter::money((float) $item->remaining_amount),
        );
    }
}
