<?php

namespace App\Services\Mail;

use App\Models\Customer;
use App\Models\Instance;
use App\Models\MailTemplate;
use App\Models\Operator;
use App\Models\Subscription;
use App\Services\Billing\TaxTreatment;
use App\Support\CompanyProfile;
use Illuminate\Support\Number;

/**
 * A template with the customer's own details put in.
 *
 * ## Why the placeholders are named in English
 *
 * They are identifiers, not prose. A template written in German uses
 * `{{customer}}` in a German sentence for the same reason a database column is
 * called `customer_id` — the name has to survive somebody switching the
 * console's language, and a placeholder that only works in one locale is a
 * template that breaks the day the interface is translated. What the OPERATOR
 * reads beside the field is translated; the token is not.
 *
 * ## Why an unknown placeholder is left standing
 *
 * A typo comes out as `{{kunde}}` in the compose field, where the operator sees
 * it before anything is sent and fixes it. Replacing it with an empty string
 * would produce "Guten Tag ," — a mail that looks sent by a machine, which is
 * exactly the impression templates exist to avoid.
 *
 * ## Why nothing is invented
 *
 * A customer with no contract gets an empty `{{plan}}`, not "Start" and not
 * "kein Paket". The operator is looking at the text and can write what belongs
 * there; a guess in a mail to a paying customer is worse than a gap.
 */
final class MailTemplateRenderer
{
    /**
     * Every placeholder, with the key of the sentence that explains it.
     *
     * The list IS the documentation: the template page renders it, and a token
     * added here appears there without anybody remembering to add it twice.
     */
    public const PLACEHOLDERS = [
        'customer' => 'templates.token.customer',
        'contact' => 'templates.token.contact',
        'email' => 'templates.token.email',
        'plan' => 'templates.token.plan',
        'amount' => 'templates.token.amount',
        'instance' => 'templates.token.instance',
        'operator' => 'templates.token.operator',
        'company' => 'templates.token.company',
    ];

    /**
     * Subject and body, filled in.
     *
     * @return array{subject: string, body: string}
     */
    public function render(MailTemplate $template, Customer $customer, ?Operator $operator = null): array
    {
        $values = $this->values($customer, $operator);

        return [
            'subject' => $this->fill($template->subject, $values),
            'body' => $this->fill($template->body, $values),
        ];
    }

    /**
     * What each placeholder stands for, for this customer.
     *
     * @return array<string, string>
     */
    public function values(Customer $customer, ?Operator $operator = null): array
    {
        $subscription = Subscription::query()
            ->where('customer_id', $customer->id)
            ->whereIn('status', ['active', 'past_due'])
            ->latest('id')
            ->first();

        $instance = Instance::query()
            ->where('customer_id', $customer->id)
            ->whereNotIn('status', ['ended', 'failed'])
            ->latest('id')
            ->first();

        return [
            'customer' => (string) $customer->name,
            // The person, where one is on record; the company otherwise. A
            // salutation is written to somebody, and "Guten Tag Beispiel GmbH"
            // is how a mail announces that it came out of a database.
            'contact' => (string) ($customer->contact_name ?: $customer->name),
            'email' => (string) $customer->email,
            'plan' => $subscription === null ? '' : __('billing.plan.'.$subscription->plan),
            // What was actually charged, because that is the figure the customer
            // knows: it is what their bank statement says.
            'amount' => $subscription === null ? '' : Number::currency(
                $this->chargedCents($subscription, $customer) / 100,
                in: (string) ($subscription->currency ?: 'EUR'),
                locale: app()->getLocale(),
            ),
            'instance' => (string) ($instance?->subdomain ?? ''),
            'operator' => (string) ($operator?->name ?? ''),
            'company' => (string) CompanyProfile::get('name', ''),
        ];
    }

    /** @param  array<string, string>  $values */
    private function fill(string $text, array $values): string
    {
        return preg_replace_callback(
            // Whitespace inside the braces is tolerated: an operator typing
            // "{{ customer }}" means the same thing as "{{customer}}", and
            // failing on a space would be a rule nobody can see.
            '/\{\{\s*([a-z_]+)\s*\}\}/i',
            // Unknown tokens are left exactly as they were — see the class
            // docblock. The operator reads this before anything is sent.
            fn (array $m) => $values[strtolower($m[1])] ?? $m[0],
            $text,
        ) ?? $text;
    }

    /**
     * The contract's monthly figure as this customer is charged it.
     *
     * Through TaxTreatment, which is the one place the amount taken is formed, and
     * therefore the same figure the Stripe Price for this contract carries. It used
     * to be a multiplication at the domestic rate written out here, with the
     * reasoning that reverse charge was a matter for documents and not for
     * sentences — and that stopped being true the moment a reverse-charge business
     * began to be charged the bare net. A mail naming a fifth more than the bank
     * statement is a mail the customer writes back about.
     */
    private function chargedCents(Subscription $subscription, Customer $customer): int
    {
        return TaxTreatment::for($customer)->chargeCents((int) $subscription->price_cents);
    }
}
