<?php

namespace App\Services\Meta;

use App\Models\Item;
use App\Models\MetaAdsProductRuleLog;
use App\Models\MetaAdsProductRuleSetting;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use RuntimeException;

class MetaAdsProductRuleService
{
    private const FEED_CACHE_KEY = 'meta_catalog_feed_xml';
    private const LABEL_FEED_CACHE_KEY = 'meta_catalog_labels_csv';

    public function settings(): MetaAdsProductRuleSetting
    {
        if (! Schema::hasTable('meta_ads_product_rule_settings')) {
            return new MetaAdsProductRuleSetting($this->defaultSettings());
        }

        return MetaAdsProductRuleSetting::query()->firstOrCreate([], $this->defaultSettings());
    }

    public function updateSettings(array $values, ?int $adminId): MetaAdsProductRuleSetting
    {
        $this->assertReady();

        $settings = $this->settings();
        $settings->fill([
            'auto_exclude_enabled' => (bool) ($values['auto_exclude_enabled'] ?? false),
            'auto_restore_enabled' => (bool) ($values['auto_restore_enabled'] ?? false),
            'minimum_final_orders' => max(1, (int) $values['minimum_final_orders']),
            'maximum_return_rate' => max(0, min(100, (float) $values['maximum_return_rate'])),
            'minimum_profit' => (float) $values['minimum_profit'],
            'high_profit_minimum_profit' => (float) $values['high_profit_minimum_profit'],
            'high_profit_minimum_roas' => max(0, (float) $values['high_profit_minimum_roas']),
            'updated_by_admin_id' => $adminId,
        ])->save();

        return $settings->fresh();
    }

    public function updateProduct(
        Item $item,
        string $priority,
        bool $excluded,
        ?string $reason,
        ?int $adminId
    ): Item {
        $this->assertReady();

        $priority = strtolower(trim($priority));
        if (! in_array($priority, ['high', 'medium', 'low'], true)) {
            throw new RuntimeException('Invalid Meta ad priority.');
        }

        $before = $this->state($item);
        $item->meta_ad_priority = $priority;
        $item->meta_ads_excluded = $excluded;
        $item->meta_ads_exclusion_mode = $excluded ? 'manual' : null;
        $item->meta_ads_exclusion_reason = $excluded
            ? mb_substr(trim((string) $reason) ?: 'Manual exclusion', 0, 255)
            : null;
        $item->meta_ads_excluded_at = $excluded ? now() : null;

        if ($excluded) {
            $item->meta_ads_high_profit = false;
        }

        $item->save();

        $after = $this->state($item);
        if ($before !== $after) {
            $this->log(
                $item,
                $excluded ? 'manual_exclude_or_update' : 'manual_restore_or_update',
                'manual',
                $before,
                $after,
                [],
                $adminId
            );
        }

        $this->clearFeedCaches();

        return $item->fresh();
    }

    public function evaluate(iterable $rows): array
    {
        $this->assertReady();

        $settings = $this->settings();
        $summary = [
            'evaluated' => 0,
            'auto_excluded' => 0,
            'auto_restored' => 0,
            'high_profit_updated' => 0,
            'unchanged' => 0,
        ];
        $feedChanged = false;

        foreach ($rows as $row) {
            $row = is_array($row) ? $row : (array) $row;
            $itemId = ctype_digit((string) ($row['product_id'] ?? ''))
                ? (int) $row['product_id']
                : 0;

            if ($itemId <= 0) {
                continue;
            }

            $item = Item::withoutGlobalScopes()->find($itemId);
            if (! $item) {
                continue;
            }

            $summary['evaluated']++;
            $decision = $this->decision($row, $settings);
            $before = $this->state($item);
            $action = null;

            if ((bool) $item->meta_ads_excluded
                && $item->meta_ads_exclusion_mode === 'manual'
            ) {
                if ((bool) $item->meta_ads_high_profit) {
                    $item->meta_ads_high_profit = false;
                    $action = 'high_profit_removed';
                }
            } else {
                if ($settings->auto_exclude_enabled && $decision['exclude']) {
                    $item->meta_ads_excluded = true;
                    $item->meta_ads_exclusion_mode = 'auto';
                    $item->meta_ads_exclusion_reason = $decision['reason'];
                    $item->meta_ads_excluded_at = $item->meta_ads_excluded_at ?: now();
                    $item->meta_ads_high_profit = false;
                    $action = 'auto_excluded';
                } elseif (
                    (bool) $item->meta_ads_excluded
                    && $item->meta_ads_exclusion_mode === 'auto'
                    && $settings->auto_exclude_enabled
                    && $settings->auto_restore_enabled
                ) {
                    $item->meta_ads_excluded = false;
                    $item->meta_ads_exclusion_mode = null;
                    $item->meta_ads_exclusion_reason = null;
                    $item->meta_ads_excluded_at = null;
                    $item->meta_ads_high_profit = $decision['high_profit'];
                    $action = 'auto_restored';
                } elseif (! (bool) $item->meta_ads_excluded
                    && (bool) $item->meta_ads_high_profit !== $decision['high_profit']
                ) {
                    $item->meta_ads_high_profit = $decision['high_profit'];
                    $action = $decision['high_profit']
                        ? 'high_profit_added'
                        : 'high_profit_removed';
                }
            }

            $after = $this->state($item);
            if ($before === $after) {
                $summary['unchanged']++;
                continue;
            }

            DB::transaction(function () use ($item, $action, $before, $after, $row) {
                $item->save();
                $this->log($item, (string) $action, 'auto', $before, $after, $row, null);
            });

            $feedChanged = true;
            if ($action === 'auto_excluded') {
                $summary['auto_excluded']++;
            } elseif ($action === 'auto_restored') {
                $summary['auto_restored']++;
            } else {
                $summary['high_profit_updated']++;
            }
        }

        if ($feedChanged) {
            $this->clearFeedCaches();
        }

        return $summary;
    }

    public function decision(array $row, MetaAdsProductRuleSetting $settings): array
    {
        $deliveredOrders = max(0, (int) ($row['delivered_orders'] ?? 0));
        $returnedOrders = max(0, (int) ($row['returned_orders'] ?? 0));
        $finalOrders = $deliveredOrders + $returnedOrders;
        $returnRate = max(0, (float) ($row['return_rate'] ?? 0));
        $profit = (float) ($row['profit'] ?? 0);
        $profitRoas = isset($row['profit_roas']) && is_numeric($row['profit_roas'])
            ? (float) $row['profit_roas']
            : null;
        $hasEnoughOrders = $finalOrders >= max(1, (int) $settings->minimum_final_orders);
        $reasons = [];

        if ($hasEnoughOrders && $returnRate > (float) $settings->maximum_return_rate) {
            $reasons[] = 'Return rate ' . number_format($returnRate, 2)
                . '% exceeds ' . number_format((float) $settings->maximum_return_rate, 2) . '%';
        }

        if ($hasEnoughOrders && $profit < (float) $settings->minimum_profit) {
            $reasons[] = 'Profit ' . number_format($profit, 2)
                . ' is below ' . number_format((float) $settings->minimum_profit, 2);
        }

        return [
            'exclude' => $reasons !== [],
            'reason' => $reasons !== [] ? implode('; ', $reasons) : null,
            'high_profit' => $hasEnoughOrders
                && $reasons === []
                && $profit >= (float) $settings->high_profit_minimum_profit
                && $profitRoas !== null
                && $profitRoas >= (float) $settings->high_profit_minimum_roas,
            'final_orders' => $finalOrders,
        ];
    }

    private function state(Item $item): array
    {
        return [
            'priority' => (string) ($item->meta_ad_priority ?: 'medium'),
            'high_profit' => (bool) $item->meta_ads_high_profit,
            'excluded' => (bool) $item->meta_ads_excluded,
            'exclusion_mode' => $item->meta_ads_exclusion_mode,
            'exclusion_reason' => $item->meta_ads_exclusion_reason,
            'excluded_at' => $item->meta_ads_excluded_at?->toIso8601String(),
        ];
    }

    private function log(
        Item $item,
        string $action,
        string $source,
        array $before,
        array $after,
        array $metrics,
        ?int $adminId
    ): void {
        MetaAdsProductRuleLog::query()->create([
            'item_id' => $item->id,
            'action' => $action,
            'source' => $source,
            'before_state' => $before,
            'after_state' => $after,
            'metrics' => $metrics,
            'admin_id' => $adminId,
        ]);
    }

    private function assertReady(): void
    {
        if (! Schema::hasTable('items')
            || ! Schema::hasColumn('items', 'meta_ad_priority')
            || ! Schema::hasTable('meta_ads_product_rule_settings')
            || ! Schema::hasTable('meta_ads_product_rule_logs')
        ) {
            throw new RuntimeException('Meta product rule tables are missing. Run migrations first.');
        }
    }

    private function defaultSettings(): array
    {
        return [
            'auto_exclude_enabled' => false,
            'auto_restore_enabled' => true,
            'minimum_final_orders' => 3,
            'maximum_return_rate' => 20,
            'minimum_profit' => 0,
            'high_profit_minimum_profit' => 1000,
            'high_profit_minimum_roas' => 1.5,
        ];
    }

    private function clearFeedCaches(): void
    {
        Cache::forget(self::FEED_CACHE_KEY);
        Cache::forget(self::LABEL_FEED_CACHE_KEY);
    }
}
