"use strict";

const express = require("express");

const {
  Category,
  Product,
  ProductVariant,
} = require("../models");

const router = express.Router();

const SITE_URL = String(
  process.env.PUBLIC_SITE_URL ||
    "https://habipoglu.com"
)
  .trim()
  .replace(/\/+$/, "");

const STATIC_PATHS = [
  "/",
  "/sikca-sorulan-sorular",
  "/gizlilik-politikasi",
  "/mesafeli-satis-sozlesmesi",
  "/iade-ve-iptal",
  "/kvkk",
];

/*
 * XML içinde özel anlam taşıyan
 * karakterlerin çıktıyı bozmasını önler.
 */
const escapeXml = (
  value
) => {
  return String(value || "")
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&apos;");
};

const normalizeSlug = (
  value
) => {
  return String(value || "")
    .trim()
    .replace(/^\/+|\/+$/g, "");
};

const buildUrl = (
  pathname
) => {
  const normalizedPath =
    pathname === "/"
      ? "/"
      : `/${String(
          pathname || ""
        ).replace(/^\/+/, "")}`;

  return `${SITE_URL}${normalizedPath}`;
};

const serializeLastModified = (
  value
) => {
  if (!value) {
    return null;
  }

  const date =
    value instanceof Date
      ? value
      : new Date(value);

  if (
    Number.isNaN(
      date.getTime()
    )
  ) {
    return null;
  }

  return date.toISOString();
};

const buildUrlNode = ({
  location,
  lastModified = null,
}) => {
  const lastmod =
    serializeLastModified(
      lastModified
    );

  const lines = [
    "  <url>",
    `    <loc>${escapeXml(
      location
    )}</loc>`,
  ];

  if (lastmod) {
    lines.push(
      `    <lastmod>${lastmod}</lastmod>`
    );
  }

  lines.push("  </url>");

  return lines.join("\n");
};

/*
 * GET /sitemap.xml
 *
 * Sitemap'e yalnız:
 * - Herkese açık sabit sayfalar
 * - Aktif kategoriler
 * - Aktif kategoriye bağlı aktif ürünler
 * - En az bir aktif varyantı bulunan ürünler
 *
 * eklenir.
 */
router.get(
  "/sitemap.xml",
  async (
    req,
    res,
    next
  ) => {
    try {
      const [
        categories,
        products,
      ] = await Promise.all([
        Category.findAll({
          where: {
            isActive: true,
          },

          attributes: [
            "id",
            "slug",
            "updatedAt",
          ],

          order: [
            [
              "sortOrder",
              "ASC",
            ],
            [
              "name",
              "ASC",
            ],
          ],
        }),

        Product.findAll({
          where: {
            status: "ACTIVE",
          },

          attributes: [
            "id",
            "slug",
            "publishedAt",
            "updatedAt",
          ],

          include: [
            {
              model: Category,
              as: "category",

              where: {
                isActive: true,
              },

              required: true,
              attributes: [],
            },

            {
              model:
                ProductVariant,
              as: "variants",

              where: {
                isActive: true,
              },

              required: true,
              attributes: [],
            },
          ],

          order: [
            [
              "updatedAt",
              "DESC",
            ],
          ],

          /*
           * hasMany variant JOIN işleminin
           * aynı ürünü birden fazla kez
           * döndürmesini engeller.
           */
          group: [
            "Product.id",
          ],

          raw: true,
        }),
      ]);

      const urlNodes = [];

      /*
       * Sabit sayfalar için yapay lastmod
       * üretilmez. Yalnız URL eklenir.
       */
      for (
        const pathname of
        STATIC_PATHS
      ) {
        urlNodes.push(
          buildUrlNode({
            location:
              buildUrl(
                pathname
              ),
          })
        );
      }

      for (
        const category of
        categories
      ) {
        const slug =
          normalizeSlug(
            category.slug
          );

        if (!slug) {
          continue;
        }

        urlNodes.push(
          buildUrlNode({
            location:
              buildUrl(
                `/kategori/${encodeURIComponent(
                  slug
                )}`
              ),

            lastModified:
              category.updatedAt,
          })
        );
      }

      /*
       * GROUP BY kullanılan raw sorguda
       * aynı ürün ihtimaline karşı ayrıca
       * Set ile koruma sağlanır.
       */
      const includedProductIds =
        new Set();

      for (
        const product of
        products
      ) {
        const productId =
          String(
            product.id || ""
          );

        const slug =
          normalizeSlug(
            product.slug
          );

        if (
          !productId ||
          !slug ||
          includedProductIds.has(
            productId
          )
        ) {
          continue;
        }

        includedProductIds.add(
          productId
        );

        urlNodes.push(
          buildUrlNode({
            location:
              buildUrl(
                `/urun/${encodeURIComponent(
                  slug
                )}`
              ),

            lastModified:
              product.updatedAt ||
              product.publishedAt,
          })
        );
      }

      const sitemap = [
        '<?xml version="1.0" encoding="UTF-8"?>',

        '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',

        ...urlNodes,

        "</urlset>",
        "",
      ].join("\n");

      res.set({
        "Content-Type":
          "application/xml; charset=utf-8",

        /*
         * Sitemap 5 dakika cache'lenir,
         * arka planda 10 dakika eski
         * sürüm kullanılabilir.
         */
        "Cache-Control":
          "public, max-age=300, stale-while-revalidate=600",

        "X-Robots-Tag":
          "noindex",
      });

      return res
        .status(200)
        .send(sitemap);
    } catch (error) {
      return next(error);
    }
  }
);

module.exports = router;