<?php

namespace App\Services\Billing;

use App\Models\Customer;
use App\Support\CompanyProfile;

/**
 * Which VAT applies to a customer, and why.
 *
 * Only the two clear-cut cases are decided here:
 *
 *  - a BUSINESS customer whose VERIFIED VAT ID belongs to another EU member
 *    state pays no VAT to us; the liability shifts to them (reverse charge),
 *    and the invoice has to say so. Unverified means domestic rate: a
 *    self-declared string must never be able to zero the tax;
 *  - everyone else is charged the seller's domestic rate.
 *
 * "Business" is the answer the customer gave when they were asked, not
 * something read out of the VAT field. Reverse charge is a business-to-business
 * rule and nothing else, so somebody who has recorded themselves as a private
 * person is charged the domestic rate whatever they have typed into `vat_id` —
 * previously they were not, and a consumer could zero their own VAT by getting
 * a number verified.
 *
 * A customer nobody has asked keeps the treatment they had: a verified VAT ID
 * still earns the reverse charge. That is deliberately NOT the protective
 * default used everywhere else in this codebase, and the reason is that the two
 * questions have different stakes. Withdrawal is a right, and guessing costs a
 * consumer something they cannot get back; VAT is a rate, and flipping every
 * unasked business onto 20 % overnight would put our invoices in disagreement
 * with contracts that are already running. So this file changes no outcome for
 * anyone whose type has never been recorded, and the moment it IS recorded, the
 * recorded answer is the only one that counts.
 *
 * Deliberately NOT handled: cross-border sales to private individuals, which
 * are taxed at the customer's own country's rate under the OSS scheme. Doing
 * that needs a maintained rate table per member state and a tax adviser's sign
 * off, not a guess in a config file — so those customers fall back to the
 * domestic rate, which over-collects rather than under-collects.
 *
 * ## The price at the till is the domestic gross — except where no VAT is charged
 *
 * The treatment above decides how a DOCUMENT is split, and it also decides the
 * amount taken from the card: chargeCents() below. For everybody who is charged
 * VAT that amount is the domestic gross, and the owner's rule holds exactly —
 * the figure on the website is the figure charged, for a private person and for
 * an Austrian business alike, because the VAT is a pass-through that a business
 * reclaims as input tax.
 *
 * A reverse-charge sale carries no VAT at all, and there the same rule points
 * the other way: the bare net figure is the whole of what is owed, so the bare
 * net figure is what is charged. Charging them the gross was a flat 20 %
 * surcharge with no VAT line anywhere for them to reclaim, and their document
 * then stated the whole of it as net at 0 % — so they self-accounted their own
 * VAT on a base a fifth too large. The rule is: Austria B2B 20 %, other EU B2B
 * without VAT.
 *
 * That is why a Stripe Price CAN ask who is buying. There are two per sellable
 * thing — the domestic gross and the bare net — both live, and the checkout
 * picks by this class. See App\Services\Billing\PlanPrices and
 * App\Services\Billing\AddonPrices.
 *
 * Stripe's own `automatic_tax` is deliberately NOT enabled anywhere. This class
 * is the single tax authority here, knowingly over-collecting on cross-border
 * B2C rather than implementing OSS; letting Stripe compute a second rate would
 * have it charge a German consumer 19 % while our document said 20 %.
 */
final readonly class TaxTreatment
{
    /** Reverse charge applies inside the EU only. */
    public const EU_MEMBER_STATES = [
        'AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'EL', 'ES', 'FI', 'FR',
        'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO',
        'SE', 'SI', 'SK',
    ];

    private function __construct(
        public float $rate,
        public bool $reverseCharge,
    ) {}

    /**
     * The seller's own rate, with nobody in particular in mind.
     *
     * From the Finance page, not from config: an operator can change the rate
     * there and cannot change a config value, and two sources for one number is
     * how an invoice ends up disagreeing with the checkout that produced it.
     * CompanyProfile falls back to the config value until a rate has been saved.
     *
     * This is the ONE place the rate is read. Everything that has to form a
     * gross figure — the price sheet, the Stripe catalogue, the module prices —
     * goes through chargeCents() below rather than multiplying by a percentage
     * of its own.
     */
    public static function domestic(): self
    {
        return new self(CompanyProfile::taxRate() / 100, false);
    }

    /**
     * The intra-EU business treatment itself, with nobody in particular in mind.
     *
     * For the catalogue mirror, which has to create the Price a reverse-charge
     * business will be sold on BEFORE any such customer has reached the checkout —
     * so it needs the treatment without having anybody to derive it from. Who
     * actually GETS it is for() below and is never decided here: this is a rate
     * and a flag, not a judgement about a customer.
     */
    public static function reverseCharge(): self
    {
        return new self(0.0, true);
    }

    /**
     * The figure the website quotes, with nobody in particular in mind.
     *
     * The public price sheet is read by visitors nobody has met, so it can only
     * state the domestic gross — and it must state the same number the ordinary
     * checkout takes, which is what makes this the domestic treatment's own
     * chargeCents() rather than a second multiplication.
     *
     * A reverse-charge business is charged less than this and is shown so on the
     * pages that know who they are. Advertising the net out here instead is not
     * an option: it would quote every private visitor a price they cannot have.
     */
    public static function advertisedCents(int $netCents): int
    {
        return self::domestic()->chargeCents($netCents);
    }

    public static function for(?Customer $customer): self
    {
        $domestic = self::domestic();

        // Unverified is the normal state, and it must cost the customer nothing
        // more than the domestic rate they would pay anyway — but it must not
        // zero the VAT either. Otherwise typing "XX123" is a discount.
        if ($customer === null || ! $customer->hasVerifiedVatId()) {
            return $domestic;
        }

        // Asked of the recorded type, not of the number. Somebody who has said
        // they are a private person is a private person, and a VAT ID on their
        // record — a former sole trader's, a colleague's, a mistake — must not
        // be able to turn that answer over. Only an explicit "consumer" refuses
        // here; an unrecorded type falls through and is treated exactly as it
        // was before this check existed (see the class comment).
        if ($customer->hasRecordedType() && ! $customer->isBusiness()) {
            return $domestic;
        }

        $vatId = $customer->normalisedVatId();
        $country = substr($vatId, 0, 2);
        $seller = strtoupper((string) config('provisioning.tax.seller_country', 'AT'));

        // Reverse charge is an intra-EU business rule: it needs a member state
        // that is not ours, and a number that at least looks like one.
        $eligible = in_array($country, self::EU_MEMBER_STATES, true)
            && $country !== $seller
            && preg_match('/^[A-Z]{2}[0-9A-Z]{8,12}$/', $vatId) === 1;

        return $eligible ? self::reverseCharge() : $domestic;
    }

    public function grossCents(int $netCents): int
    {
        return (int) round($netCents * (1 + $this->rate));
    }

    /**
     * What THIS customer's card is charged for a net catalogue figure.
     *
     * The one place the amount taken is formed, and therefore the one place that
     * decides which of a sellable thing's two Stripe Prices a checkout uses, what
     * the proof register expects to have been taken, and what a document has to
     * total to. A second reader forming the figure from a rate of its own is how
     * the charge, the Price and the invoice come to disagree.
     *
     * Written on the reverse-charge flag rather than on the rate, although at a
     * rate of nought grossCents() would return the same number. The two are
     * different questions that happen to share an answer here: grossCents() says
     * how a document is SPLIT, and this says what is TAKEN — and if cross-border
     * B2C is ever taxed at the customer's own rate under OSS, the two part
     * company and this must go on charging what was quoted.
     */
    public function chargeCents(int $netCents): int
    {
        return $this->reverseCharge ? $netCents : $this->grossCents($netCents);
    }

    /**
     * The rate in the form the invoice arithmetic works in — 2000 for 20 %.
     *
     * Here rather than at each call site, because the conversion was written out
     * by hand where an invoice was issued and nowhere else, and a second reader
     * of `rate` doing its own multiplication is how two documents end up at two
     * rates.
     */
    public function basisPoints(): int
    {
        return (int) round($this->rate * 10000);
    }

    public function percentLabel(): string
    {
        return rtrim(rtrim(number_format($this->rate * 100, 1, ',', '.'), '0'), ',');
    }
}
