<?php

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

/**
 * The fourteen days a consumer may change their mind in.
 *
 * EU distance selling — in Austria the FAGG, in Germany §312g BGB — gives a
 * consumer fourteen days from the conclusion of the contract to withdraw from
 * it without giving a reason and get their money back. A business customer has
 * no such right, which is exactly why `customers.customer_type` had to exist
 * first.
 *
 * The end of the window is STAMPED, not derived, for the same reason
 * `pending_effective_at` is: it is a date the customer was told, and a date that
 * is recomputed on every read is a date that can move. `started_at` is where it
 * comes from and it is in Subscription::FROZEN, so the opening moment cannot be
 * rewritten either.
 *
 * `immediate_start_consent_at` is the other half of the law and the reason this
 * is not simply "refund everything". A service the consumer expressly asked us
 * to begin during the window is a service they owe the pro-rata value of when
 * they withdraw (FAGG §16) — but only if they expressly asked for it AND were
 * told they would owe it. Without that consent on record they owe nothing, so
 * the consent is a timestamp on the purchase, not a flag on a form somewhere.
 *
 * The refund columns record what was actually sent back and, if the payment
 * provider refused, that it was not. A withdrawal that ended a service and
 * issued documents while silently failing to move the money is the worst of the
 * possible outcomes, so the failure is on the contract where an operator will
 * see it rather than in a log line that scrolls away.
 */
return new class extends Migration
{
    public function up(): void
    {
        Schema::table('orders', function (Blueprint $table) {
            // Recorded at the checkout, where the consumer ticks it, and copied
            // onto the contract when one is opened. On the purchase rather than
            // only on the contract because the consent belongs to the ORDER: it
            // is what was agreed at the moment of buying, and it exists before
            // there is any contract to hang it on.
            $table->timestamp('immediate_start_consent_at')->nullable()->after('status');

            // What a refund is actually issued against. Stripe refunds a
            // payment, not a subscription and not a checkout session, so the
            // handle has to be kept at the moment the payment is reported.
            $table->string('stripe_payment_intent_id')->nullable()->after('stripe_subscription_id');
            $table->string('stripe_invoice_id')->nullable()->after('stripe_payment_intent_id');
        });

        Schema::table('subscriptions', function (Blueprint $table) {
            $table->timestamp('withdrawal_ends_at')->nullable()->after('cancelled_at');
            $table->timestamp('immediate_start_consent_at')->nullable()->after('withdrawal_ends_at');
            $table->timestamp('withdrawn_at')->nullable()->after('immediate_start_consent_at');

            // 'portal' when the customer did it themselves, 'operator' when it
            // came in by telephone or by post and somebody typed it in. Which
            // of the two it was is part of the evidence that a withdrawal was
            // declared at all.
            $table->string('withdrawal_channel', 16)->nullable()->after('withdrawn_at');
            $table->foreignId('withdrawal_recorded_by')->nullable()->after('withdrawal_channel')
                ->constrained('operators')->nullOnDelete();

            // GROSS, because that is what was taken and what goes back.
            $table->integer('withdrawal_refund_cents')->nullable()->after('withdrawal_recorded_by');
            $table->string('withdrawal_refund_reference')->nullable()->after('withdrawal_refund_cents');
            $table->string('withdrawal_refund_error')->nullable()->after('withdrawal_refund_reference');
        });

        // Contracts that already exist had a window too — it opened when they
        // began, whether or not anything in the code was counting. Filled in so
        // that a contract signed yesterday is not quietly treated as one whose
        // window never existed; for everything older the date is simply in the
        // past, which is the correct answer.
        // Read in full before anything is written. Streaming a cursor over the
        // table this loop then UPDATEs is exactly the shape that stalls on an
        // unbuffered MySQL connection, and a migration that hangs on a live
        // database is worse than one that holds a few thousand rows in memory.
        //
        // Fourteen written out rather than read from WithdrawalRight::WINDOW_DAYS:
        // a migration states what it did on the day it ran, and a constant that
        // moves later would silently rewrite what this one is recorded as having
        // done.
        $rows = DB::table('subscriptions')->whereNotNull('started_at')->select('id', 'started_at')->get();

        foreach ($rows as $row) {
            DB::table('subscriptions')->where('id', $row->id)->update([
                'withdrawal_ends_at' => Carbon::parse($row->started_at)->addDays(14),
            ]);
        }
    }

    public function down(): void
    {
        Schema::table('subscriptions', function (Blueprint $table) {
            $table->dropForeign(['withdrawal_recorded_by']);
            $table->dropColumn([
                'withdrawal_ends_at', 'immediate_start_consent_at', 'withdrawn_at',
                'withdrawal_channel', 'withdrawal_recorded_by',
                'withdrawal_refund_cents', 'withdrawal_refund_reference', 'withdrawal_refund_error',
            ]);
        });

        Schema::table('orders', function (Blueprint $table) {
            $table->dropColumn(['immediate_start_consent_at', 'stripe_payment_intent_id', 'stripe_invoice_id']);
        });
    }
};
