<?php

namespace App\Filament\Resources\JobSources;

use App\Filament\Resources\JobSources\Pages\EditJobSource;
use App\Filament\Resources\JobSources\Pages\ListJobSources;
use App\Models\JobSource;
use BackedEnum;
use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction;
use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use SensitiveParameter;

class JobSourceResource extends Resource
{
    protected static ?string $model = JobSource::class;

    protected static string|BackedEnum|null $navigationIcon = Heroicon::Link;

    protected static ?string $navigationLabel = 'Job Sources';

    protected static string|\UnitEnum|null $navigationGroup = 'Jobs';

    protected static ?int $navigationSort = 10;

    public static function form(Schema $schema): Schema
    {
        return $schema
            ->components([
                Section::make('Source identity')
                    ->description('Define the source and choose how jobs should be imported.')
                    ->schema([
                        TextInput::make('name')
                            ->required()
                            ->maxLength(255),

                        TextInput::make('slug')
                            ->required()
                            ->unique(ignoreRecord: true)
                            ->rules(['alpha_dash:ascii'])
                            ->maxLength(255)
                            ->helperText('Stable identifier used by sync commands, for example remotive-api.'),

                        Select::make('type')
                            ->required()
                            ->live()
                            ->options(JobSource::typeOptions())
                            ->afterStateUpdated(function (Set $set, ?string $state): void {
                                if ($state === JobSource::TYPE_RSS) {
                                    $set('sync_settings.item_path', 'channel.item');
                                    $set('sync_settings.source_url_path', 'link');
                                    $set('sync_settings.apply_url_path', '');
                                    $set('sync_settings.external_id_path', 'guid');
                                    $set('sync_settings.posted_at_path', 'pubDate');
                                    $set('sync_settings.category_path', 'category');
                                    $set('sync_settings.description_path', 'description');
                                    $set('sync_settings.skills_path', 'skills');
                                    $set('sync_settings.employment_type_path', 'type');
                                    $set('sync_settings.location_text_path', 'region');
                                    $set('sync_settings.split_company_from_title', true);

                                    return;
                                }

                                if ($state === JobSource::TYPE_XML) {
                                    $set('sync_settings.item_path', 'jobs.job');
                                    $set('sync_settings.split_company_from_title', false);
                                }
                            })
                            ->helperText(fn (Get $get): string => self::typeHelper((string) $get('type'))),

                        Toggle::make('is_active')
                            ->label('Active')
                            ->default(true)
                            ->helperText('Only active sources are queued by jobs:sync-sources.'),

                        TextInput::make('base_url')
                            ->label('Source website URL')
                            ->url()
                            ->maxLength(2048)
                            ->helperText('Public website for attribution or admin reference.'),

                        Textarea::make('attribution_text')
                            ->maxLength(1000)
                            ->helperText('Displayed as source attribution for imported third-party jobs.')
                            ->columnSpanFull(),
                    ])
                    ->columns(2),

                Section::make('Endpoint and schedule')
                    ->description('Shown for source types that can be synced automatically.')
                    ->schema([
                        TextInput::make('feed_url')
                            ->label('Endpoint / feed URL')
                            ->url()
                            ->required(fn (Get $get): bool => self::usesEndpoint((string) $get('type')))
                            ->maxLength(2048)
                            ->helperText(fn (Get $get): string => self::endpointHelper((string) $get('type'))),

                        Select::make('request_method')
                            ->label('Request method')
                            ->options([
                                'get' => 'GET',
                                'post' => 'POST',
                            ])
                            ->default('get')
                            ->visible(fn (Get $get): bool => self::usesJsonRequest((string) $get('type')))
                            ->helperText('Most public feeds use GET. Use POST only when the source documentation requires it.'),

                        Select::make('sync_frequency')
                            ->options([
                                'manual' => 'Manual only',
                                'hourly' => 'Hourly',
                                'daily' => 'Daily',
                                'weekly' => 'Weekly',
                            ])
                            ->default('manual')
                            ->helperText('Scheduling frequency metadata. Cron still runs through the Laravel scheduler/command.'),

                        TextInput::make('rate_limit_per_minute')
                            ->label('Rate limit per minute')
                            ->integer()
                            ->minValue(0)
                            ->helperText('Optional admin reference. Respect source API/feed terms.'),
                    ])
                    ->columns(2)
                    ->visible(fn (Get $get): bool => self::usesEndpoint((string) $get('type'))),

                Section::make('Authentication')
                    ->description('Credentials are only for permitted API/feed access. Secret value is stored encrypted.')
                    ->schema([
                        Select::make('auth_type')
                            ->options(JobSource::authOptions())
                            ->default(JobSource::AUTH_NONE)
                            ->live(),

                        TextInput::make('auth_header_name')
                            ->label('API key header name')
                            ->maxLength(255)
                            ->required(fn (Get $get): bool => $get('auth_type') === JobSource::AUTH_API_KEY_HEADER)
                            ->visible(fn (Get $get): bool => $get('auth_type') === JobSource::AUTH_API_KEY_HEADER)
                            ->helperText('Example: X-API-Key. Do not put the secret value here.'),

                        TextInput::make('auth_username')
                            ->label('Basic auth username')
                            ->maxLength(255)
                            ->required(fn (Get $get): bool => $get('auth_type') === JobSource::AUTH_BASIC)
                            ->visible(fn (Get $get): bool => $get('auth_type') === JobSource::AUTH_BASIC),

                        TextInput::make('auth_secret')
                            ->label('Auth secret / token')
                            ->password()
                            ->revealable()
                            ->autocomplete('new-password')
                            ->formatStateUsing(fn (): ?string => null)
                            ->dehydrated(fn (#[SensitiveParameter] $state): bool => filled($state))
                            ->helperText('Leave blank to keep the saved encrypted secret.')
                            ->visible(fn (Get $get): bool => $get('auth_type') !== JobSource::AUTH_NONE),
                    ])
                    ->columns(2)
                    ->visible(fn (Get $get): bool => in_array((string) $get('type'), [JobSource::TYPE_API, JobSource::TYPE_JSON], true)),

                Section::make('Request options')
                    ->description('Non-secret headers and query/body parameters sent to the source.')
                    ->schema([
                        KeyValue::make('request_headers')
                            ->keyLabel('Header')
                            ->valueLabel('Value')
                            ->helperText('Use for non-secret headers like Accept or User-Agent. Store API secrets in Auth secret.')
                            ->columnSpanFull(),

                        KeyValue::make('query_parameters')
                            ->keyLabel('Parameter')
                            ->valueLabel('Value')
                            ->helperText('For GET requests these are query parameters. For POST JSON/API requests these are sent as the JSON body.')
                            ->columnSpanFull(),
                    ])
                    ->columns(1)
                    ->visible(fn (Get $get): bool => self::usesEndpoint((string) $get('type'))),

                Section::make('JSON/API mapping')
                    ->description('Use these when the source returns a JSON response.')
                    ->schema([
                        TextInput::make('sync_settings.items_path')
                            ->label('Items path')
                            ->default('jobs')
                            ->helperText('Dot path to the job list in the JSON response. Use blank if the response itself is an array.'),

                        TextInput::make('sync_settings.timeout')
                            ->label('Timeout seconds')
                            ->integer()
                            ->default(20)
                            ->minValue(5)
                            ->maxValue(120),

                        Toggle::make('sync_settings.expire_missing')
                            ->label('Expire jobs missing from latest sync')
                            ->helperText('Only enable when the source feed reliably returns the complete active job list.'),
                    ])
                    ->columns(3)
                    ->visible(fn (Get $get): bool => self::usesJsonRequest((string) $get('type'))),

                Section::make('RSS/XML mapping')
                    ->description('Use these when the source returns RSS or XML. Paths are relative to each job item.')
                    ->schema([
                        TextInput::make('sync_settings.item_path')
                            ->label('Item path')
                            ->default(fn (Get $get): string => $get('type') === JobSource::TYPE_RSS ? 'channel.item' : 'jobs.job')
                            ->helperText('RSS usually uses channel.item. Generic XML depends on the source document.'),

                        TextInput::make('sync_settings.title_path')
                            ->label('Title path')
                            ->default('title'),

                        TextInput::make('sync_settings.company_path')
                            ->label('Company path')
                            ->default('company'),

                        Toggle::make('sync_settings.split_company_from_title')
                            ->label('Split company from title')
                            ->helperText('Use when titles look like "Company: Role", as We Work Remotely does.')
                            ->default(fn (Get $get): bool => $get('type') === JobSource::TYPE_RSS),

                        TextInput::make('sync_settings.fallback_company')
                            ->label('Fallback company')
                            ->helperText('Optional. Use only when the source does not publish company names and this fallback is acceptable.'),

                        TextInput::make('sync_settings.source_url_path')
                            ->label('Source URL path')
                            ->default('link'),

                        TextInput::make('sync_settings.apply_url_path')
                            ->label('Apply URL path')
                            ->default(fn (Get $get): string => $get('type') === JobSource::TYPE_RSS ? '' : 'apply_url')
                            ->helperText('If blank/missing, source URL is used as the apply URL.'),

                        TextInput::make('sync_settings.external_id_path')
                            ->label('External ID path')
                            ->default('guid'),

                        TextInput::make('sync_settings.posted_at_path')
                            ->label('Posted date path')
                            ->default('pubDate'),

                        TextInput::make('sync_settings.category_path')
                            ->label('Category path')
                            ->default('category'),

                        TextInput::make('sync_settings.skills_path')
                            ->label('Skills/tags path')
                            ->default('skills'),

                        TextInput::make('sync_settings.employment_type_path')
                            ->label('Employment type path')
                            ->default('type'),

                        TextInput::make('sync_settings.location_text_path')
                            ->label('Location/region path')
                            ->default('region')
                            ->helperText('For feeds with text regions like Anywhere in the World.'),

                        TextInput::make('sync_settings.description_path')
                            ->label('Description path')
                            ->default('description'),

                        TextInput::make('sync_settings.timeout')
                            ->label('Timeout seconds')
                            ->integer()
                            ->default(20)
                            ->minValue(5)
                            ->maxValue(120),

                        Toggle::make('sync_settings.expire_missing')
                            ->label('Expire jobs missing from latest sync'),
                    ])
                    ->columns(3)
                    ->visible(fn (Get $get): bool => in_array((string) $get('type'), [JobSource::TYPE_RSS, JobSource::TYPE_XML], true)),

                Section::make('Manual source notes')
                    ->description('Manual sources do not sync automatically. Add jobs from Imported Jobs and attribute them to this source.')
                    ->schema([
                        Textarea::make('notes')
                            ->label('Internal notes')
                            ->maxLength(4000)
                            ->columnSpanFull(),
                    ])
                    ->visible(fn (Get $get): bool => $get('type') === JobSource::TYPE_MANUAL),
            ])
            ->columns(1);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                TextColumn::make('name')
                    ->searchable()
                    ->sortable(),

                TextColumn::make('slug')
                    ->searchable(),

                TextColumn::make('type')
                    ->badge()
                    ->sortable(),

                TextColumn::make('sync_frequency')
                    ->label('Frequency')
                    ->badge()
                    ->sortable(),

                IconColumn::make('is_active')
                    ->boolean()
                    ->sortable(),

                TextColumn::make('feed_url')
                    ->label('Endpoint')
                    ->limit(44)
                    ->toggleable(),

                TextColumn::make('last_synced_at')
                    ->dateTime()
                    ->sortable(),

                TextColumn::make('last_failed_at')
                    ->dateTime()
                    ->sortable(),

                TextColumn::make('last_failure_reason')
                    ->label('Failure')
                    ->limit(50)
                    ->toggleable(isToggledHiddenByDefault: true),

                TextColumn::make('created_at')
                    ->dateTime()
                    ->sortable(),
            ])
            ->filters([
                SelectFilter::make('type')
                    ->options(JobSource::typeOptions()),

                SelectFilter::make('is_active')
                    ->label('Active')
                    ->options([
                        '1' => 'Active',
                        '0' => 'Inactive',
                    ]),
            ])
            ->recordActions([
                EditAction::make(),
                DeleteAction::make(),
            ])
            ->defaultSort('created_at', 'desc');
    }

    public static function getPages(): array
    {
        return [
            'index' => ListJobSources::route('/'),
            'edit' => EditJobSource::route('/{record}/edit'),
        ];
    }

    private static function usesEndpoint(string $type): bool
    {
        return in_array($type, [JobSource::TYPE_API, JobSource::TYPE_JSON, JobSource::TYPE_RSS, JobSource::TYPE_XML], true);
    }

    private static function usesJsonRequest(string $type): bool
    {
        return in_array($type, [JobSource::TYPE_API, JobSource::TYPE_JSON], true);
    }

    private static function typeHelper(string $type): string
    {
        return match ($type) {
            JobSource::TYPE_API => 'Use this for official REST APIs that return JSON.',
            JobSource::TYPE_JSON => 'Use this for public JSON feeds.',
            JobSource::TYPE_RSS => 'Use this for RSS feeds with job items.',
            JobSource::TYPE_XML => 'Use this for generic XML feeds with configurable item paths.',
            default => 'Use this for manually curated or employer-submitted jobs.',
        };
    }

    private static function endpointHelper(string $type): string
    {
        return match ($type) {
            JobSource::TYPE_API => 'Official API endpoint URL. Verify rate limits and terms before enabling sync.',
            JobSource::TYPE_RSS => 'RSS feed URL. The default item path is channel.item.',
            JobSource::TYPE_XML => 'XML feed URL. Configure the item path and field paths below.',
            default => 'JSON feed URL. The response should include job records and source/apply URLs.',
        };
    }
}
