<?php

namespace App\Master;

use App\Mail\InvoiceNotification;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Mtc\MercuryDataModels\Invoice;
use Mtc\MercuryDataModels\InvoiceStatus;
use Mtc\MercuryDataModels\Tenant;
use Mtc\MercuryDataModels\TenantBillable;

class InvoiceGenerator
{
    /**
     * Create invoice for client
     *
     * @param Tenant $tenant
     * @return void
     */
    public function create(Tenant $tenant): Invoice
    {
        $invoice = $this->generateInvoice($tenant);
        $this->generatePdf($invoice);
        $this->notify($invoice);

        $tenant->update([
            'last_invoice_created_at' => $invoice->created_at,
            'next_invoice_due_at' => Carbon::now()->addMonth(),
        ]);

        return $invoice;
    }

    /**
     * Generate an invoice when tier is upgraded (Part-value of the month)
     *
     * @param Tenant $tenant
     */
    public function makeForTierUpgrade(Tenant $tenant)
    {
        $percent_of_billing_period_left = $tenant->next_invoice_due_at?->diffInDays(now(), true)
            / Carbon::now()->daysInMonth;
        $tier = $tenant->billables()
            ->whereHas('billable', fn ($query) => $query->where('type', 'tier'))
            ->firstOrFail();

        $invoice_value = $tier->price / ($percent_of_billing_period_left ?: 1);

        /** @var Invoice $invoice */
        $invoice = Invoice::query()->create([
            'tenant_id' => $tenant->id,
            'email' => $tenant->billingDetails->billing_email,
            'status' => InvoiceStatus::OPEN->value,
            'due_at' => Carbon::now()->addDays(14),
            'amount' => $invoice_value,
            'vat_rate' => $tenant->billingDetails->vat_rate,
        ]);

        $this->invoiceSellerAddress($tenant, $invoice);
        $this->invoiceRecipientAddress($tenant, $invoice);

        $invoice->items()
            ->create([
                'billable_id' => $tier->billable->id,
                'name' => $tier->billable->name,
                'price' => $tier->price,
                'discounted_price' => $invoice_value,
                'quantity' => 1,
                'line_total' => $invoice_value,
            ]);

        $this->generatePdf($invoice);
        $this->notify($invoice);
    }

    /**
     * Generate Invoice DB record
     *
     * @param Tenant $tenant
     * @return Invoice
     * @throws \Exception
     */
    private function generateInvoice(Tenant $tenant): Invoice
    {
        /** @var Invoice $invoice */
        $invoice = Invoice::query()->create([
            'tenant_id' => $tenant->id,
            'email' => $tenant->billingDetails->billing_email,
            'status' => InvoiceStatus::OPEN->value,
            'due_at' => Carbon::now()->addDays(14),
            'amount' => $this->getInvoiceTotalValue($tenant),
            'vat_rate' => $tenant->billingDetails->vat_rate,
        ]);

        $this->invoiceSellerAddress($tenant, $invoice);
        $this->invoiceRecipientAddress($tenant, $invoice);
        $this->invoiceItems($tenant, $invoice);
        return $invoice;
    }

    /**
     * Attach seller details - this is system owner
     *
     * @param Tenant $tenant
     * @param Invoice $invoice
     * @return void
     */
    private function invoiceSellerAddress(Tenant $tenant, Invoice $invoice)
    {
        $invoice->addresses()->create([
            'type' => 'seller',
            'first_name' => config('invoices.seller.name'),
            'last_name' => '',
            'address1' => config('invoices.seller.address1'),
            'address2' => config('invoices.seller.address2'),
            'city' => config('invoices.seller.city'),
            'county' => config('invoices.seller.county'),
            'postcode' => config('invoices.seller.postcode'),
            'country' => config('invoices.seller.country'),
        ]);
    }

    /**
     * Attach recipient details - this is site owner
     *
     * @param Tenant $tenant
     * @param Invoice $invoice
     * @return void
     */
    private function invoiceRecipientAddress(Tenant $tenant, Invoice $invoice)
    {
        $invoice->addresses()->create([
            'type' => 'recipient',
            'first_name' => $tenant->billingDetails->first_name,
            'last_name' => $tenant->billingDetails->last_name,
            'address1' => $tenant->billingDetails->address1,
            'address2' => $tenant->billingDetails->address2,
            'city' => $tenant->billingDetails->city,
            'county' => $tenant->billingDetails->county,
            'postcode' => $tenant->billingDetails->postcode,
            'country' => $tenant->billingDetails->country,
        ]);
    }

    /**
     * Attach items of the invoice
     *
     * @param Tenant $tenant
     * @param Invoice $invoice
     * @return void
     */
    private function invoiceItems(Tenant $tenant, Invoice $invoice)
    {
        return $tenant->billables
            ->filter(fn (TenantBillable $billable) => $billable->isApplicableNow())
            ->each(fn ($billable) => $invoice->items()->create([
                'billable_id' => $billable->id,
                'name' => $billable->billable->name,
                'price' => $billable->price,
                'discounted_price' => $billable->discounted_price,
                'quantity' => $billable->quantity,
                'line_total' => $billable->quantity * ($billable->discounted_price ?: $billable->price),
            ]));
    }

    /**
     * Generate pdf file for invoice
     *
     * @param Invoice $invoice
     * @return void
     */
    private function generatePdf(Invoice $invoice)
    {
        $date = Carbon::now()->format('Y-m');
        $tenant = Str::slug($invoice->tenant->name);
        $invoice_path = "invoices/{$date}/invoice-{$date}-{$tenant}.pdf";
        $pdf = Pdf::loadView('invoices.pdf', compact('invoice'))->output();

        if (Storage::disk('file-storage')->exists('invoices/' . $date) === false) {
            Storage::disk('file-storage')->makeDirectory($date);
        }

        if (Storage::disk('file-storage')->put($invoice_path, $pdf)) {
            $invoice->update([
                'pdf_path' => $invoice_path
            ]);
        }
    }

    /**
     * Send invoice to customer
     *
     * @param Invoice $invoice
     * @return void
     */
    private function notify(Invoice $invoice)
    {
        Mail::to($invoice->email)
            ->bcc(config('mail.billing'))
            ->send(new InvoiceNotification($invoice));
    }

    /**
     * Get the invoice total value
     *
     * @param Tenant $tenant
     * @return float
     */
    private function getInvoiceTotalValue(Tenant $tenant): float
    {
        return $tenant->billables
            ->filter(fn (TenantBillable $billable) => $billable->isApplicableNow())
            ->sum(fn ($billable) => $billable->quantity * ($billable->discounted_price ?: $billable->price));
    }
}
