<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Jobs\SyncCustomerQualityAudienceJob;
use App\Models\CustomerQualityFlag;
use App\Models\CustomerQualityMetric;
use App\Models\User;
use App\Services\Customer\CustomerQualityService;
use App\Services\Meta\MetaCustomerAudienceSyncService;
use Brian2694\Toastr\Facades\Toastr;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Validator;

class CustomerQualityController extends Controller
{
    public function __construct(
        private readonly CustomerQualityService $customerQualityService,
        private readonly MetaCustomerAudienceSyncService $metaAudienceSyncService
    ) {}

    public function index(Request $request)
    {
        $customers = $this->customerQualityService->report($request);
        $totals = $this->customerQualityService->totals($request);
        $settings = $this->customerQualityService->settings();
        $audienceCounts = $this->customerQualityService->audienceCounts();
        $audienceSummaries = $this->customerQualityService->audienceSummaries();
        $syncLogs = $this->customerQualityService->latestLogs(12);
        $metaWriteEligibility = $this->customerQualityService->metaWriteEligibility($settings);
        $audienceStatuses = collect(array_keys(CustomerQualityService::AUDIENCE_NAMES))
            ->mapWithKeys(fn ($key) => [$key => $this->customerQualityService->audienceStatus($key)])
            ->all();
        $preview = null;

        if ($request->filled('preview_audience')) {
            $audienceKey = (string) $request->query('preview_audience');
            if (array_key_exists($audienceKey, CustomerQualityService::AUDIENCE_NAMES)) {
                $preview = $this->customerQualityService->audiencePlan($audienceKey);
            }
        }

        return view('admin-views.customer-quality.index', compact(
            'customers',
            'totals',
            'settings',
            'audienceCounts',
            'audienceSummaries',
            'audienceStatuses',
            'syncLogs',
            'metaWriteEligibility',
            'preview'
        ));
    }

    public function refreshMetrics()
    {
        try {
            $result = $this->customerQualityService->refreshMetrics();
            Toastr::success(sprintf(
                'Customer quality cache refreshed. Metrics: %d, High quality: %d, Risk: %d.',
                $result['metric_count'],
                $result['high_quality_count'],
                $result['risk_count']
            ));
        } catch (\Throwable $throwable) {
            Toastr::error($throwable->getMessage());
        }

        return back();
    }

    public function syncSelected(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'audience_key' => 'required|string|in:'.implode(',', array_keys(CustomerQualityService::AUDIENCE_NAMES)),
            'mode' => 'nullable|string|in:dry_run,test_upload,live',
            'customer_keys' => 'required|array|min:1',
            'customer_keys.*' => 'string|max:120',
            'confirm_live_sync' => 'nullable|accepted',
            'confirm_test_upload' => 'nullable|accepted',
        ]);

        if ($validator->fails()) {
            Toastr::error($validator->errors()->first());

            return back()->withInput();
        }

        return $this->handleSyncRequest(
            request: $request,
            metricKeys: $request->input('customer_keys', []),
            syncAllEligible: false
        );
    }

    public function syncAll(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'audience_key' => 'required|string|in:'.implode(',', array_keys(CustomerQualityService::AUDIENCE_NAMES)),
            'mode' => 'nullable|string|in:dry_run,test_upload,live',
            'confirm_live_sync' => 'nullable|accepted',
            'confirm_test_upload' => 'nullable|accepted',
        ]);

        if ($validator->fails()) {
            Toastr::error($validator->errors()->first());

            return back()->withInput();
        }

        return $this->handleSyncRequest(
            request: $request,
            metricKeys: null,
            syncAllEligible: true
        );
    }

    public function updateSettings(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'auto_sync_enabled' => 'nullable|boolean',
            'automatic_removal_enabled' => 'nullable|boolean',
            'lookalike_sync_enabled' => 'nullable|boolean',
            'max_removal_percentage' => 'required|numeric|min:0|max:20',
            'auto_exclusions' => 'required|array',
            'auto_exclusions.enabled' => 'nullable|boolean',
            'auto_exclusions.match_admin_staff_tables' => 'nullable|boolean',
            'auto_exclusions.keywords' => 'nullable|string',
            'auto_exclusions.emails' => 'nullable|string',
            'auto_exclusions.phones' => 'nullable|string',
            'min_delivered_orders' => 'required|integer|min:0',
            'min_receive_rate' => 'required|numeric|min:0|max:100',
            'max_return_rate' => 'required|numeric|min:0|max:100',
            'max_cancel_rate' => 'required|numeric|min:0|max:100',
            'min_delivered_amount' => 'required|numeric|gt:0',
            'min_average_order_value' => 'required|numeric|gt:0',
            'dry_run_expires_minutes' => 'required|integer|min:5|max:1440',
            'country_code' => 'required|string|size:2',
            'status_mappings' => 'required|array',
            'status_mappings.delivered' => 'required|string',
            'status_mappings.returned' => 'required|string',
            'status_mappings.cancelled' => 'required|string',
            'lookalike' => 'required|array',
            'lookalike.country' => 'required|string|size:2',
            'lookalike.ratio' => 'required|numeric|min:0.01|max:0.2',
            'audiences' => 'nullable|array',
            'audiences.'.CustomerQualityService::HIGH_AUDIENCE_KEY.'.meta_audience_id' => 'nullable|string|max:191',
            'audiences.'.CustomerQualityService::HIGH_AUDIENCE_KEY.'.lookalike_audience_id' => 'nullable|string|max:191',
            'audiences.'.CustomerQualityService::RISK_AUDIENCE_KEY.'.meta_audience_id' => 'nullable|string|max:191',
        ]);

        if ($validator->fails()) {
            Toastr::error($validator->errors()->first());

            return back()->withInput();
        }

        $mappingErrors = $this->customerQualityService->statusMappingErrors(
            $request->input('status_mappings', [])
        );
        if ($mappingErrors !== []) {
            Toastr::error($mappingErrors[0]);

            return back()->withInput();
        }

        $autoSyncEnabled = $request->boolean('auto_sync_enabled');
        $automaticRemovalEnabled = $request->boolean('automatic_removal_enabled');
        if ($autoSyncEnabled && config('queue.default') === 'sync') {
            Toastr::error('Auto Sync requires an asynchronous queue driver and a running queue worker.');

            return back()->withInput();
        }

        $metaWriteEligibility = $this->customerQualityService->metaWriteEligibility();
        if (($autoSyncEnabled || $automaticRemovalEnabled) && ! data_get($metaWriteEligibility, 'allowed', false)) {
            Toastr::error(
                'Meta write safety prerequisites are not satisfied. '
                .implode(' ', (array) data_get($metaWriteEligibility, 'reasons', []))
            );

            return back()->withInput();
        }

        if ($request->boolean('lookalike_sync_enabled') && blank($request->input('audiences.'.CustomerQualityService::HIGH_AUDIENCE_KEY.'.lookalike_audience_id'))) {
            Toastr::error('Lookalike Sync cannot be enabled without a validated Lookalike Audience ID.');

            return back()->withInput();
        }

        $settingsPayload = array_replace($request->all(), [
            'auto_sync_enabled' => $autoSyncEnabled,
            'automatic_removal_enabled' => $automaticRemovalEnabled,
            'lookalike_sync_enabled' => $request->boolean('lookalike_sync_enabled'),
            'auto_exclusions' => array_replace((array) $request->input('auto_exclusions', []), [
                'enabled' => $request->boolean('auto_exclusions.enabled'),
                'match_admin_staff_tables' => $request->boolean('auto_exclusions.match_admin_staff_tables'),
            ]),
        ]);

        try {
            $refreshResult = DB::transaction(function () use ($settingsPayload) {
                $this->customerQualityService->updateSettings($settingsPayload);

                return $this->customerQualityService->refreshMetrics();
            });
        } catch (\Throwable $throwable) {
            Toastr::error('Settings were not changed because the metric refresh failed: '.$throwable->getMessage());

            return back()->withInput();
        }

        Toastr::success(sprintf(
            'Customer quality settings and metrics updated. Metrics: %d, High quality: %d, Risk: %d.',
            $refreshResult['metric_count'],
            $refreshResult['high_quality_count'],
            $refreshResult['risk_count']
        ));

        return back();
    }

    public function updateFlag(User $customer, Request $request)
    {
        $validator = Validator::make($request->all(), [
            'flag_action' => 'required|string|in:blacklist,fake,clear',
            'reason' => 'nullable|string|max:191',
        ]);

        if ($validator->fails()) {
            Toastr::error($validator->errors()->first());

            return back()->withInput();
        }

        $action = $request->input('flag_action');

        if ($action === 'clear') {
            CustomerQualityFlag::query()->where('user_id', $customer->id)->delete();
            Toastr::success('Customer quality flag cleared.');

            return back();
        }

        $flag = CustomerQualityFlag::query()->firstOrNew(['user_id' => $customer->id]);
        if (! $flag->exists) {
            $flag->created_by_admin_id = auth('admin')->id();
        }

        $flag->fill([
            'is_blacklisted' => $action === 'blacklist',
            'is_fake' => $action === 'fake',
            'reason' => $request->input('reason'),
            'updated_by_admin_id' => auth('admin')->id(),
        ])->save();

        Toastr::success('Customer quality flag updated. Refresh the customer quality cache before the next dry run.');

        return back();
    }

    public function updateExclusion(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'metric_key' => 'required|string|max:100',
            'exclude_action' => 'required|string|in:exclude,include',
            'category' => 'required_if:exclude_action,exclude|nullable|string|in:admin,staff,test,own_store,internal,other',
            'reason' => 'nullable|string|max:191',
        ]);

        if ($validator->fails()) {
            Toastr::error($validator->errors()->first());

            return back()->withInput();
        }

        $metric = CustomerQualityMetric::query()
            ->where('metric_key', $request->input('metric_key'))
            ->first();

        if (! $metric) {
            Toastr::error('Customer quality metric was not found.');

            return back();
        }

        $isExcluded = $request->input('exclude_action') === 'exclude';
        $this->customerQualityService->updateMetaExclusion(
            metricKey: $metric->metric_key,
            userId: $metric->user_id,
            isExcluded: $isExcluded,
            category: $isExcluded ? (string) $request->input('category', 'internal') : 'other',
            reason: $isExcluded ? $request->input('reason') : 'Manual exclusion cleared',
            adminId: auth('admin')->id()
        );

        Toastr::success($isExcluded
            ? 'Customer excluded from Meta audiences. Run a fresh dry run before the next sync.'
            : 'Customer Meta audience exclusion cleared. Run a fresh dry run before the next sync.');

        return back();
    }

    public function refreshAudienceStatus(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'audience_key' => 'required|string|in:'.implode(',', array_keys(CustomerQualityService::AUDIENCE_NAMES)),
        ]);

        if ($validator->fails()) {
            Toastr::error($validator->errors()->first());

            return back();
        }

        try {
            $status = $this->metaAudienceSyncService->refreshAudienceStatus((string) $request->input('audience_key'));
            Toastr::success('Meta audience status: '.ucfirst((string) data_get($status, 'state', 'unknown')).'.');
        } catch (\Throwable $throwable) {
            Toastr::error($throwable->getMessage());
        }

        return back();
    }

    public function createLookalike(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'audience_key' => 'required|string|in:'.CustomerQualityService::HIGH_AUDIENCE_KEY,
            'confirm_lookalike' => 'required|accepted',
        ]);

        if ($validator->fails()) {
            Toastr::error($validator->errors()->first());

            return back();
        }

        try {
            $id = $this->metaAudienceSyncService->createBangladeshLookalikeAudience(
                (string) $request->input('audience_key')
            );
            Toastr::success('Bangladesh 1% Lookalike audience ready: '.$id);
        } catch (\Throwable $throwable) {
            Toastr::error($throwable->getMessage());
        }

        return back();
    }

    public function invalidPhonesCsv()
    {
        abort_unless(Schema::hasTable('customer_quality_metrics'), 404);

        $rows = DB::table('customer_quality_metrics')
            ->whereNull('phone_hash')
            ->orderBy('metric_key')
            ->get(['metric_key', 'user_id', 'customer_name', 'phone', 'customer_email', 'phone_validation_reason']);

        return response()->streamDownload(function () use ($rows) {
            $stream = fopen('php://output', 'w');
            fputcsv($stream, ['metric_key', 'customer_id', 'customer_name', 'phone', 'email', 'invalid_reason']);
            foreach ($rows as $row) {
                fputcsv($stream, [(string) $row->metric_key, (string) $row->user_id, (string) $row->customer_name, (string) $row->phone, (string) $row->customer_email, (string) $row->phone_validation_reason]);
            }
            fclose($stream);
        }, 'customer-quality-invalid-phones-'.now()->format('Ymd-His').'.csv', ['Content-Type' => 'text/csv; charset=UTF-8']);
    }

    private function handleSyncRequest(Request $request, ?array $metricKeys, bool $syncAllEligible)
    {
        $audienceKey = (string) $request->input('audience_key');
        $mode = in_array($request->input('mode'), ['test_upload', 'live'], true)
            ? (string) $request->input('mode')
            : 'dry_run';

        try {
            if ($mode === 'dry_run') {
                $log = $this->metaAudienceSyncService->dryRun(
                    audienceKey: $audienceKey,
                    metricKeys: $metricKeys,
                    syncAllEligible: $syncAllEligible,
                    adminId: auth('admin')->id()
                );

                Toastr::success($this->syncMessage($log));

                return back();
            }

            if ($mode === 'test_upload' && ! $request->boolean('confirm_test_upload')) {
                Toastr::error('Please confirm the one-customer Meta test upload after reviewing the matching dry run.');

                return back()->withInput();
            }

            if ($mode === 'live' && ! $request->boolean('confirm_live_sync')) {
                Toastr::error('Please confirm live Meta sync after reviewing the matching dry run.');

                return back()->withInput();
            }

            if ($mode === 'live') {
                $metaWriteEligibility = $this->customerQualityService->metaWriteEligibility();
                if (! data_get($metaWriteEligibility, 'allowed', false)) {
                    Toastr::error(
                        'Live Meta Sync safety prerequisites are not satisfied. '
                        .implode(' ', (array) data_get($metaWriteEligibility, 'reasons', []))
                    );

                    return back()->withInput();
                }
            }

            if (config('queue.default') === 'sync') {
                Toastr::error('Live Meta Sync requires an async queue driver and a running queue worker.');

                return back()->withInput();
            }

            $plan = $this->customerQualityService->audiencePlan($audienceKey, $metricKeys, $syncAllEligible);
            $dryRun = $this->customerQualityService->latestValidDryRun(
                $audienceKey,
                $plan['scope_hash'],
                $plan['checksum']
            );

            if (! $dryRun) {
                Toastr::error('No matching non-expired dry run found for this exact scope and checksum.');

                return back()->withInput();
            }

            if ($mode === 'live' && ! $this->customerQualityService->hasSuccessfulTestUpload(
                $audienceKey,
                $plan['scope_hash'],
                $plan['checksum']
            )) {
                Toastr::error('Run a successful accepted-and-cleaned Test Upload for this checksum before Live Meta Sync.');

                return back()->withInput();
            }

            SyncCustomerQualityAudienceJob::dispatch(
                $dryRun->id,
                $audienceKey,
                $metricKeys,
                auth('admin')->id(),
                $mode
            );

            Toastr::success($mode === 'test_upload'
                ? 'One-customer Meta Test Upload queued after checksum validation.'
                : 'Live Meta Sync queued after dry-run, checksum and accepted-and-cleaned test-upload validation.');
        } catch (\Throwable $throwable) {
            Toastr::error($throwable->getMessage());
        }

        return back();
    }

    private function syncMessage($log): string
    {
        return sprintf(
            'Customer quality %s ready. Eligible: %d, Add: %d, Remove: %d, Skipped: %d, Checksum: %s, Expires: %s.',
            str_replace('_', ' ', $log->mode),
            $log->eligible_count,
            $log->add_count,
            $log->remove_count,
            $log->skipped_count,
            $log->checksum ?: 'n/a',
            $log->expires_at?->toDateTimeString() ?: 'n/a'
        );
    }
}
