<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

/**
 * Pricing & discount overhaul.
 *
 * 1. pricing_rule_branches pivot — one rule, many branches (no more per-branch duplicates)
 * 2. Picker columns (pinned quick buttons, display order, recipe provenance)
 * 3. Governed manual discount columns (approval + role ceiling)
 * 4. is_stackable default flips to FALSE (best-of-one is the normal case)
 * 5. BACKFILL: percentage adjustment_value -> basis points (was 100x too small)
 * 6. BACKFILL: conditions -> canonical vocabulary (engine could never read the old keys)
 */
return new class extends Migration
{
    /** Canonical condition keys the engine reads. */
    private const RANGE_MIN = ['min_age', 'min_months', 'min_children', 'min_family_size',
        'order_number', 'sibling_order', 'min_enrollments', 'min_points', 'min'];
    private const RANGE_MAX = ['max_age', 'max_months', 'max_family_size', 'max'];
    private const LIST_KEYS = ['target_gender', 'gender', 'target_classification', 'classifications',
        'target_branch_id', 'branch_ids', 'values', 'in'];

    public function up(): void
    {
        // ─── 1. Branch pivot ──────────────────────────────────────────
        Schema::create('pricing_rule_branches', function (Blueprint $table) {
            $table->id();
            $table->foreignId('academy_id')->constrained('academies');
            $table->foreignId('pricing_rule_id')->constrained('pricing_rules')->cascadeOnDelete();
            $table->foreignId('branch_id')->constrained('branches')->cascadeOnDelete();
            $table->timestamps();

            $table->unique(['pricing_rule_id', 'branch_id'], 'pricing_rule_branches_unique');
            $table->index(['academy_id', 'branch_id']);
        });

        // ─── 2 & 3. New columns ───────────────────────────────────────
        Schema::table('pricing_rules', function (Blueprint $table) {
            $table->boolean('is_pinned')->default(false);
            $table->string('pin_icon', 16)->nullable();
            $table->integer('display_order')->default(0);
            $table->string('recipe_key', 40)->nullable();
            $table->boolean('is_manual')->default(false);
            $table->boolean('requires_approval')->default(false);
            $table->integer('min_role_level')->nullable();
        });

        DB::statement('CREATE INDEX pricing_rules_picker_idx ON pricing_rules (academy_id, is_active, is_pinned, display_order)');

        // ─── 4. Stackable default flips ───────────────────────────────
        DB::statement('ALTER TABLE pricing_rules ALTER COLUMN is_stackable SET DEFAULT false');

        // Existing rules have never applied correctly (see backfills below), so
        // re-baseline them on the new default rather than leaving a mixed estate.
        DB::table('pricing_rules')->update(['is_stackable' => false]);

        // ─── 5. Percentage -> basis points ────────────────────────────
        // Engine divides by 10000; both authoring screens stored a plain percent.
        // Guard: only values that look like a plain percent (<= 100) are scaled.
        foreach (['pricing_rules', 'promotions'] as $table) {
            DB::table($table)
                ->whereIn('adjustment_type', ['percentage_discount', 'percentage_increase'])
                ->where('adjustment_value', '>', 0)
                ->where('adjustment_value', '<=', 100)
                ->update(['adjustment_value' => DB::raw('adjustment_value * 100')]);
        }

        // ─── 6. Conditions -> canonical vocabulary ────────────────────
        $this->migrateConditions();

        // ─── Backfill branch pivot from the single FK ─────────────────
        DB::statement('
            INSERT INTO pricing_rule_branches (academy_id, pricing_rule_id, branch_id, created_at, updated_at)
            SELECT academy_id, id, branch_id, NOW(), NOW()
            FROM pricing_rules
            WHERE branch_id IS NOT NULL AND deleted_at IS NULL
            ON CONFLICT DO NOTHING
        ');
    }

    /**
     * Rewrite every rule's conditions onto the keys PricingService actually reads.
     *
     * Anything that cannot be mapped with confidence deactivates the rule instead of
     * guessing — a wrong discount costs real money, an inactive one costs a phone call.
     */
    private function migrateConditions(): void
    {
        DB::table('pricing_rules')->orderBy('id')->chunkById(200, function ($rules) {
            foreach ($rules as $rule) {
                $old = json_decode($rule->conditions ?? '{}', true) ?: [];
                if (empty($old)) {
                    continue;
                }

                [$new, $confident] = $this->mapConditions($rule->rule_type, $old);

                $update = ['conditions' => json_encode($new, JSON_UNESCAPED_UNICODE)];

                if (! $confident) {
                    $update['is_active'] = false;
                    $meta = json_decode($rule->metadata ?? '{}', true) ?: [];
                    $meta['migration_note'] = 'تم إيقاف القاعدة تلقائياً: الشروط القديمة غير قابلة للتحويل — يرجى إعادة إنشائها';
                    $meta['legacy_conditions'] = $old;
                    $update['metadata'] = json_encode($meta, JSON_UNESCAPED_UNICODE);
                }

                DB::table('pricing_rules')->where('id', $rule->id)->update($update);
            }
        });
    }

    /** @return array{0: array, 1: bool} [canonical conditions, confident] */
    private function mapConditions(string $ruleType, array $old): array
    {
        $new = [];
        $confident = true;

        // Range-style rules: collapse every legacy min/max spelling onto min/max.
        foreach (self::RANGE_MIN as $k) {
            if (isset($old[$k]) && is_numeric($old[$k])) {
                $new['min'] = (int) $old[$k];
                break;
            }
        }
        foreach (self::RANGE_MAX as $k) {
            if (isset($old[$k]) && is_numeric($old[$k])) {
                $new['max'] = (int) $old[$k];
                break;
            }
        }

        // List-style rules: collapse onto values[].
        foreach (self::LIST_KEYS as $k) {
            if (! isset($old[$k]) || $old[$k] === '' || $old[$k] === null) {
                continue;
            }
            $v = $old[$k];
            $new['values'] = array_values(array_filter(is_array($v) ? $v : [$v], fn ($x) => $x !== '' && $x !== null));
            break;
        }

        // Type-specific passthroughs.
        if ($ruleType === 'seasonal') {
            if (isset($old['months']) && is_array($old['months'])) {
                $new['months'] = array_map('intval', $old['months']);
            } elseif (isset($old['season_start']) || isset($old['season_end'])) {
                $confident = false; // date range -> month list is a guess; make a human decide
            }
        }

        if ($ruleType === 'day_time') {
            if (isset($old['days']) && is_array($old['days'])) {
                $new['days'] = array_map(fn ($d) => strtolower((string) $d), $old['days']);
            }
            foreach ([['start_time', 'after_hour'], ['end_time', 'before_hour']] as [$from, $to]) {
                if (! empty($old[$from])) {
                    $new[$to] = (int) explode(':', (string) $old[$from])[0];
                }
            }
            foreach (['after_hour', 'before_hour'] as $k) {
                if (isset($old[$k]) && is_numeric($old[$k])) {
                    $new[$k] = (int) $old[$k];
                }
            }
        }

        if ($ruleType === 'enrollment_timing') {
            foreach (['before_date', 'after_date'] as $k) {
                if (! empty($old[$k])) {
                    $new[$k] = $old[$k];
                }
            }
            foreach (['within_days', 'days_before_start'] as $k) {
                if (isset($old[$k]) && is_numeric($old[$k])) {
                    $new['days_before_start'] = (int) $old[$k];
                    break;
                }
            }
        }

        // Legacy conditions existed but nothing survived the mapping — do not let the
        // rule silently become an unconditional academy-wide discount.
        if (empty($new)) {
            $confident = false;
        }

        return [$new, $confident];
    }

    public function down(): void
    {
        Schema::dropIfExists('pricing_rule_branches');

        DB::statement('DROP INDEX IF EXISTS pricing_rules_picker_idx');

        Schema::table('pricing_rules', function (Blueprint $table) {
            $table->dropColumn([
                'is_pinned', 'pin_icon', 'display_order', 'recipe_key',
                'is_manual', 'requires_approval', 'min_role_level',
            ]);
        });

        DB::statement('ALTER TABLE pricing_rules ALTER COLUMN is_stackable SET DEFAULT true');
    }
};
