<?php

namespace App\Controllers;

use App\Libraries\LeadMailer;
use App\Libraries\QuotationPdf;
use App\Models\ClientModel;
use App\Models\InvoiceModel;
use App\Models\QuotationItemModel;
use App\Models\QuotationModel;
use App\Models\SettingModel;

class Quotations extends BaseController
{
    private const STATUSES = ["draft", "sent", "accepted", "declined"];

    public function index()
    {
        $search = trim((string) $this->request->getGet("search"));
        $status = (string) $this->request->getGet("status");
        $model = new QuotationModel();
        $model
            ->select(
                "quotations.*, clients.first_name, clients.last_name, clients.email",
            )
            ->join("clients", "clients.id = quotations.client_id");

        if ($search !== "") {
            $model
                ->groupStart()
                ->like("quotations.quotation_number", $search)
                ->orLike("clients.first_name", $search)
                ->orLike("clients.last_name", $search)
                ->orLike("clients.email", $search)
                ->groupEnd();
        }
        if (in_array($status, self::STATUSES, true)) {
            $model->where("quotations.status", $status);
        }

        $totalValue = (new QuotationModel())->selectSum("total")->first();

        return view("admin/quotations/index", [
            "quotations" => $model
                ->orderBy("quotations.created_at", "DESC")
                ->findAll(250),
            "search" => $search,
            "activeStatus" => $status,
            "statuses" => self::STATUSES,
            "metrics" => [
                "total" => (new QuotationModel())->countAll(),
                "draft" => (new QuotationModel())
                    ->where("status", "draft")
                    ->countAllResults(),
                "accepted" => (new QuotationModel())
                    ->where("status", "accepted")
                    ->countAllResults(),
                "value" => (float) ($totalValue["total"] ?? 0),
            ],
        ]);
    }

    public function create()
    {
        if ($this->request->getMethod() === "post") {
            return $this->saveQuotation();
        }

        return view("admin/quotations/form", [
            "quotation" => null,
            "items" => [],
            "clients" => $this->clients(),
            "statuses" => self::STATUSES,
            "mode" => "create",
        ]);
    }

    public function show($id)
    {
        $quotation = $this->quotationWithClient((int) $id);
        if (!$quotation) {
            return $this->notFound();
        }

        return view("admin/quotations/show", [
            "quotation" => $quotation,
            "items" => $this->items((int) $id),
            "linkedInvoice" => (new InvoiceModel())
                ->where("quotation_id", (int) $id)
                ->first(),
        ]);
    }

    public function edit($id)
    {
        $quotation = (new QuotationModel())->find((int) $id);
        if (!$quotation) {
            return $this->notFound();
        }

        if ($this->request->getMethod() === "post") {
            return $this->saveQuotation((int) $id);
        }

        return view("admin/quotations/form", [
            "quotation" => $quotation,
            "items" => $this->items((int) $id),
            "clients" => $this->clients(),
            "statuses" => self::STATUSES,
            "mode" => "edit",
        ]);
    }

    public function delete($id)
    {
        $quotation = (new QuotationModel())->find((int) $id);
        if (!$quotation) {
            return $this->notFound();
        }
        if (
            (new InvoiceModel())
                ->where("quotation_id", (int) $id)
                ->countAllResults() > 0
        ) {
            return redirect()
                ->back()
                ->with(
                    "error",
                    "This quotation has an invoice and cannot be deleted.",
                );
        }

        $db = db_connect();
        $db->transStart();
        $db->table("quotation_items")
            ->where("quotation_id", (int) $id)
            ->delete();
        (new QuotationModel())->delete((int) $id);
        $db->transComplete();

        if (!$db->transStatus()) {
            return redirect()
                ->back()
                ->with(
                    "error",
                    "The quotation could not be deleted. Please try again.",
                );
        }

        return redirect()
            ->to(base_url("admin/quotations"))
            ->with(
                "success",
                "Quotation " . $quotation["quotation_number"] . " was deleted.",
            );
    }

    public function download($id)
    {
        $quotation = $this->quotationWithClient((int) $id);
        if (!$quotation) {
            return $this->notFound();
        }

        $pdf = (new QuotationPdf())->generate(
            $quotation,
            $this->items((int) $id),
        );
        $filename = $quotation["quotation_number"] . ".pdf";

        return $this->response
            ->setContentType("application/pdf")
            ->setHeader(
                "Content-Disposition",
                'attachment; filename="' . $filename . '"',
            )
            ->setHeader("Content-Length", (string) strlen($pdf))
            ->setBody($pdf);
    }

    public function emailPdf($id)
    {
        $quotation = $this->quotationWithClient((int) $id);
        if (!$quotation) {
            return $this->notFound();
        }

        $pdf = (new QuotationPdf())->generate(
            $quotation,
            $this->items((int) $id),
        );
        $tempFile = tempnam(WRITEPATH . "cache", "quotation_");
        if ($tempFile === false) {
            return redirect()
                ->back()
                ->with("error", "The PDF could not be prepared.");
        }

        $pdfFile = $tempFile;
        if (file_put_contents($pdfFile, $pdf) === false) {
            unlink($pdfFile);
            return redirect()
                ->back()
                ->with("error", "The PDF could not be prepared.");
        }
        try {
            $sent = (new LeadMailer())->sendQuotation(
                $quotation["email"],
                $this->emailVariables($quotation, $this->items((int) $id)),
                $pdfFile,
            );
        } finally {
            if (is_file($pdfFile)) {
                unlink($pdfFile);
            }
        }

        if (!$sent) {
            return redirect()
                ->back()
                ->with(
                    "error",
                    "The quotation PDF could not be emailed. Check SMTP settings and confirm the quotation email template is active.",
                );
        }

        $this->markAsSent((int) $id);
        return redirect()
            ->back()
            ->with(
                "success",
                "Quotation PDF emailed to " . $quotation["email"] . ".",
            );
    }

    public function emailQuotation($id)
    {
        $quotation = $this->quotationWithClient((int) $id);
        if (!$quotation) {
            return $this->notFound();
        }

        $sent = (new LeadMailer())->sendQuotationEmail(
            $quotation["email"],
            $this->emailVariables($quotation, $this->items((int) $id)),
        );
        if (!$sent) {
            return redirect()
                ->back()
                ->with(
                    "error",
                    "The quotation could not be emailed. Check SMTP settings and confirm the quotation email template is active.",
                );
        }

        $this->markAsSent((int) $id);
        return redirect()
            ->back()
            ->with(
                "success",
                "Quotation emailed to " . $quotation["email"] . ".",
            );
    }

    private function saveQuotation(?int $id = null)
    {
        $rules = [
            "client_id" => "required|integer|greater_than[0]",
            "quotation_date" => "required|valid_date[Y-m-d]",
            "valid_until" => "permit_empty|valid_date[Y-m-d]",
            "status" =>
                "required|in_list[" . implode(",", self::STATUSES) . "]",
            "notes" => "permit_empty|max_length[3000]",
        ];

        if (!$this->validate($rules)) {
            return $this->validationError(
                implode(" ", $this->validator->getErrors()),
            );
        }

        $clientId = (int) $this->request->getPost("client_id");
        if (!(new ClientModel())->find($clientId)) {
            return $this->validationError("Please select a valid client.");
        }

        $quotationDate = (string) $this->request->getPost("quotation_date");
        $validUntil = (string) $this->request->getPost("valid_until");
        if ($validUntil !== "" && $validUntil < $quotationDate) {
            return $this->validationError(
                "The valid-until date cannot be before the quotation date.",
            );
        }

        [$items, $itemError] = $this->prepareItems();
        if ($itemError !== null) {
            return $this->validationError($itemError);
        }

        $total = array_sum(array_column($items, "line_total"));
        $quotationData = [
            "client_id" => $clientId,
            "quotation_date" => $quotationDate,
            "valid_until" => $validUntil !== "" ? $validUntil : null,
            "status" => (string) $this->request->getPost("status"),
            "notes" => trim((string) $this->request->getPost("notes")),
            "subtotal" => number_format($total, 2, ".", ""),
            "total" => number_format($total, 2, ".", ""),
        ];

        $db = db_connect();
        $db->transStart();
        $quotations = new QuotationModel();
        if ($id === null) {
            $quotationData["quotation_number"] =
                "PENDING-" . bin2hex(random_bytes(6));
            $id = (int) $quotations->insert($quotationData);
            $quotations->update($id, [
                "quotation_number" =>
                    "BS-Q-" .
                    date("Y") .
                    "-" .
                    str_pad($id, 5, "0", STR_PAD_LEFT),
            ]);
        } else {
            if (!$quotations->find($id)) {
                return $this->notFound();
            }
            $quotations->update($id, $quotationData);
            $db->table("quotation_items")->where("quotation_id", $id)->delete();
        }

        foreach ($items as $index => $item) {
            $item["quotation_id"] = $id;
            $item["sort_order"] = $index;
            (new QuotationItemModel())->insert($item);
        }
        $db->transComplete();

        if (!$db->transStatus()) {
            return $this->validationError(
                "The quotation could not be saved. Please try again.",
            );
        }

        return redirect()
            ->to(base_url("admin/quotations/" . $id))
            ->with(
                "success",
                $this->request->getPost("status") === "draft"
                    ? "Quotation saved as draft."
                    : "Quotation saved successfully.",
            );
    }

    private function prepareItems(): array
    {
        $names = (array) $this->request->getPost("item_name");
        $descriptions = (array) $this->request->getPost("description");
        $quantities = (array) $this->request->getPost("quantity");
        $prices = (array) $this->request->getPost("price");
        $items = [];

        foreach ($names as $index => $name) {
            $name = trim((string) $name);
            $description = trim((string) ($descriptions[$index] ?? ""));
            $quantity = $quantities[$index] ?? "";
            $price = $prices[$index] ?? "";

            if (
                $name === "" &&
                $description === "" &&
                $quantity === "" &&
                $price === ""
            ) {
                continue;
            }
            if ($name === "" || strlen($name) > 150) {
                return [[], "Every item needs a valid item name."];
            }
            if (strlen($description) > 2000) {
                return [
                    [],
                    "Item descriptions cannot exceed 2,000 characters.",
                ];
            }
            if (!is_numeric($quantity) || (float) $quantity <= 0) {
                return [[], "Every item quantity must be greater than zero."];
            }
            if (!is_numeric($price) || (float) $price < 0) {
                return [[], "Every item price must be zero or greater."];
            }

            $quantity = round((float) $quantity, 2);
            $price = round((float) $price, 2);
            $items[] = [
                "item_name" => $name,
                "description" => $description,
                "quantity" => number_format($quantity, 2, ".", ""),
                "price" => number_format($price, 2, ".", ""),
                "line_total" => number_format(
                    round($quantity * $price, 2),
                    2,
                    ".",
                    "",
                ),
            ];
        }

        return $items
            ? [$items, null]
            : [[], "Add at least one quotation item."];
    }

    private function clients(): array
    {
        return (new ClientModel())
            ->orderBy("first_name", "ASC")
            ->orderBy("last_name", "ASC")
            ->findAll();
    }

    private function items(int $quotationId): array
    {
        return (new QuotationItemModel())
            ->where("quotation_id", $quotationId)
            ->orderBy("sort_order", "ASC")
            ->findAll();
    }

    private function quotationWithClient(int $id): ?array
    {
        return (new QuotationModel())
            ->select(
                "quotations.*, clients.first_name, clients.last_name, clients.email, clients.mobile, clients.address",
            )
            ->join("clients", "clients.id = quotations.client_id")
            ->where("quotations.id", $id)
            ->first();
    }

    private function emailVariables(array $quotation, array $items = []): array
    {
        $settings = (new SettingModel())->values([
            "whatsapp_number" => "919876543210",
        ]);

        $itemLines = [];
        foreach ($items as $item) {
            $itemLines[] =
                $item["item_name"] .
                " × " .
                number_format((float) $item["quantity"], 2) .
                " — ₹" .
                number_format((float) $item["line_total"], 2);
        }

        return [
            "name" => $quotation["first_name"],
            "business_name" =>
                $quotation["first_name"] . " " . $quotation["last_name"],
            "quotation_number" => $quotation["quotation_number"],
            "items_summary" => implode("\n", $itemLines),
            "amount" => "₹" . number_format((float) $quotation["total"], 2),
            "valid_until" => $quotation["valid_until"]
                ? date("d M Y", strtotime($quotation["valid_until"]))
                : "until further notice",
            "attachment_name" => $quotation["quotation_number"] . ".pdf",
            "whatsapp_url" =>
                "https://wa.me/" . ltrim($settings["whatsapp_number"], "+"),
        ];
    }

    private function markAsSent(int $id): void
    {
        (new QuotationModel())->update($id, ["status" => "sent"]);
    }

    private function validationError(string $message)
    {
        return redirect()->back()->withInput()->with("error", $message);
    }

    private function notFound()
    {
        return redirect()
            ->to(base_url("admin/quotations"))
            ->with("error", "Quotation not found.");
    }
}
