import axios from "axios";
import moment from "moment-jalaali";
import { parse } from "node-html-parser";

const BASE_URL = "https://omidib.com";
const BLOG_URL = `${BASE_URL}/blog`;
const DEFAULT_START_PAGE = 1;
const DEFAULT_END_PAGE = 40;
const REQUEST_TIMEOUT = 30000;
const MAX_EXCERPT_LENGTH = 220;
const CONTENT_TYPE_EXTENSION_MAP = {
  "image/jpeg": "jpg",
  "image/jpg": "jpg",
  "image/png": "png",
  "image/webp": "webp",
  "image/gif": "gif",
  "image/svg+xml": "svg",
  "image/avif": "avif",
};

const LEGACY_CATEGORY_TO_NEWS_CATEGORY = {
  "majale-khabari": "announcements",
  "gozaresh-amari": "statistical-reports",
  "gozaresh-mali": "financial-reports",
  entesharat: "publications",
  mkalat: "analysis",
};

const SECTION_CATEGORY_MAP = {
  "مجله خبری": "announcements",
  "گزارش آماری": "statistical-reports",
  "گزارش های مالی": "financial-reports",
  "گزارش‌های مالی": "financial-reports",
  انتشارات: "publications",
  مقالات: "analysis",
};

moment.loadPersian({ dialect: "persian-modern" });

let dbResourcesPromise = null;

const getDbResources = async () => {
  if (!dbResourcesPromise) {
    dbResourcesPromise = Promise.all([
      import("$connections/index.js"),
      import("$models/index.js"),
    ]).then(([connectionsModule, modelsModule]) => ({
      mongoConnection: connectionsModule.mongoConnection,
      models: modelsModule.default,
    }));
  }

  return dbResourcesPromise;
};

const parseArgs = (argv = process.argv.slice(2)) => {
  const options = {
    startPage: DEFAULT_START_PAGE,
    endPage: DEFAULT_END_PAGE,
    limit: null,
    dryRun: false,
    verbose: false,
  };

  argv.forEach((arg) => {
    if (arg === "--dry-run") {
      options.dryRun = true;
      return;
    }

    if (arg === "--verbose") {
      options.verbose = true;
      return;
    }

    const [flag, rawValue] = arg.split("=");
    const value = Number.parseInt(rawValue, 10);

    if (flag === "--page" && Number.isInteger(value) && value > 0) {
      options.startPage = value;
      options.endPage = value;
      return;
    }

    if (flag === "--start-page" && Number.isInteger(value) && value > 0) {
      options.startPage = value;
      return;
    }

    if (flag === "--end-page" && Number.isInteger(value) && value > 0) {
      options.endPage = value;
      return;
    }

    if (flag === "--limit" && Number.isInteger(value) && value > 0) {
      options.limit = value;
    }
  });

  if (options.endPage < options.startPage) {
    throw new Error("endPage must be greater than or equal to startPage");
  }

  return options;
};

const absoluteUrl = (value = "") => {
  if (!value) return "";
  try {
    return new URL(value, BASE_URL).toString();
  } catch (error) {
    return value;
  }
};

const fetchHtml = async (url) => {
  const response = await axios.get(url, {
    timeout: REQUEST_TIMEOUT,
    responseType: "text",
    headers: {
      "User-Agent":
        "Mozilla/5.0 (compatible; OmidIBLegacyNewsImporter/1.0; +https://omidib.com)",
      Accept: "text/html,application/xhtml+xml",
    },
  });

  return response.data;
};

const fetchBinary = async (url) => {
  const response = await axios.get(url, {
    timeout: REQUEST_TIMEOUT,
    responseType: "arraybuffer",
    headers: {
      "User-Agent":
        "Mozilla/5.0 (compatible; OmidIBLegacyNewsImporter/1.0; +https://omidib.com)",
      Accept: "image/*,*/*;q=0.8",
    },
  });

  return {
    content: Buffer.from(response.data),
    contentType: response.headers["content-type"] || "application/octet-stream",
  };
};

const toNumericSlug = (legacyPostId) => {
  const normalizedId = String(legacyPostId || "").trim();

  if (!/^\d+$/.test(normalizedId)) {
    throw new Error(`Invalid legacy post id: ${legacyPostId}`);
  }

  if (normalizedId.length > 6) {
    throw new Error(
      `Legacy post id ${legacyPostId} cannot be converted to a 6 digit slug safely`,
    );
  }

  return normalizedId.padStart(6, "0");
};

const stripHashPrefix = (value = "") =>
  value
    .replace(/^#/, "")
    .replace(/\s+/g, " ")
    .trim();

const decodeHtmlText = (value = "") => {
  if (!value) return "";

  const decoded = parse(`<span>${value}</span>`).text;

  return decoded
    .replace(/&nbsp;|&#160;|&#xa0;/gi, " ")
    .replace(/\u00a0/g, " ");
};

const sanitizeText = (value = "") =>
  decodeHtmlText(value)
    .replace(/\s+/g, " ")
    .trim();

const truncate = (value = "", maxLength = MAX_EXCERPT_LENGTH) => {
  const normalized = sanitizeText(value);

  if (normalized.length <= maxLength) {
    return normalized;
  }

  return `${normalized.slice(0, maxLength - 1).trim()}…`;
};

const inferExtension = (url = "", contentType = "") => {
  const normalizedType = contentType.split(";")[0].trim().toLowerCase();
  if (CONTENT_TYPE_EXTENSION_MAP[normalizedType]) {
    return CONTENT_TYPE_EXTENSION_MAP[normalizedType];
  }

  try {
    const pathname = new URL(url).pathname;
    const extension = pathname.split(".").pop()?.trim().toLowerCase();
    if (extension && extension.length <= 5) {
      return extension;
    }
  } catch (error) {
    // Fall through to default.
  }

  return "bin";
};

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

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

  return Number.isNaN(date.valueOf()) ? null : date;
};

const normalizeContentHtml = (html = "") => {
  const wrapper = parse(`<div id="content-root">${html}</div>`);
  const root = wrapper.querySelector("#content-root");

  if (!root) {
    return html.trim();
  }

  root.querySelectorAll("[src]").forEach((node) => {
    const src = node.getAttribute("src");
    if (src) node.setAttribute("src", absoluteUrl(src));
  });

  root.querySelectorAll("[href]").forEach((node) => {
    const href = node.getAttribute("href");
    if (href) node.setAttribute("href", absoluteUrl(href));
  });

  return root.innerHTML.trim();
};

const extractCategorySlug = (documentRoot, articleSchema = {}) => {
  const breadcrumbCategoryLink = documentRoot
    .querySelectorAll(".breadcrumb a")
    .find((anchor) => anchor.getAttribute("href")?.includes("/blog?category="));

  if (breadcrumbCategoryLink) {
    try {
      const categoryUrl = new URL(
        breadcrumbCategoryLink.getAttribute("href"),
        BASE_URL,
      );
      const legacyCategory = categoryUrl.searchParams.get("category");
      const normalizedLegacyCategory = legacyCategory?.trim().toLowerCase();

      if (normalizedLegacyCategory) {
        return (
          LEGACY_CATEGORY_TO_NEWS_CATEGORY[normalizedLegacyCategory] ||
          normalizedLegacyCategory
        );
      }
    } catch (error) {
      // Ignore malformed links and fall back to section text.
    }
  }

  const section = sanitizeText(articleSchema.articleSection || "");
  return SECTION_CATEGORY_MAP[section] || "announcements";
};

const applyCategoryHeuristics = ({ category, title, content }) => {
  const normalizedTitle = sanitizeText(title);
  const imageCount = (content.match(/<img\b/gi) || []).length;

  if (
    normalizedTitle.startsWith("گزارش تصویری") ||
    normalizedTitle.includes("آلبوم") ||
    imageCount >= 3
  ) {
    return "multimedia";
  }

  return category || "announcements";
};

const upsertCoverImageFile = async ({ slug, coverImageUrl }) => {
  const { models } = await getDbResources();

  if (!slug) {
    return null;
  }

  if (!coverImageUrl) {
    await models.file.findOneAndDelete({ name: slug });
    return null;
  }

  const { content, contentType } = await fetchBinary(coverImageUrl);
  const extension = inferExtension(coverImageUrl, contentType);

  return models.file.findOneAndUpdate(
    { name: slug },
    {
      $set: {
        name: slug,
        content,
        type: contentType,
        extension,
      },
    },
    {
      new: true,
      upsert: true,
      setDefaultsOnInsert: true,
    },
  );
};

const extractJsonLd = (documentRoot) => {
  const scriptNode = documentRoot
    .querySelectorAll('script[type="application/ld+json"]')
    .find((node) => node.innerText?.includes('"@type": "BlogPosting"'));

  if (!scriptNode) {
    return {};
  }

  try {
    return JSON.parse(scriptNode.innerText);
  } catch (error) {
    return {};
  }
};

const extractListPagePosts = (html) => {
  const documentRoot = parse(html);

  return documentRoot.querySelectorAll("#posts article").reduce((items, article) => {
    const id = article.getAttribute("id") || "";
    const postIdMatch = id.match(/^post-(\d+)$/);
    const linkNode = article.querySelector("a.blog-card");
    const url = linkNode?.getAttribute("href");

    if (!postIdMatch || !url) {
      return items;
    }

    items.push({
      legacyPostId: postIdMatch[1],
      url: absoluteUrl(url),
    });

    return items;
  }, []);
};

const extractArticleRecord = (html, listItem) => {
  const documentRoot = parse(html);
  const articleSchema = extractJsonLd(documentRoot);
  const contentNode = documentRoot.querySelector("#postBody");
  const titleNode = documentRoot.querySelector("h1");
  const coverNode = documentRoot.querySelector(".post-card .cover img");
  const tagNodes = documentRoot.querySelectorAll(".tags a");
  const firstParagraph = contentNode?.querySelector("p");

  const title = sanitizeText(articleSchema.headline || titleNode?.text || "");
  const excerptSource =
    articleSchema.description || firstParagraph?.text || contentNode?.text || title;
  const tags = tagNodes
    .map((node) => stripHashPrefix(node.text))
    .filter(Boolean);
  const publishedAt = toValidDate(articleSchema.datePublished);
  const modifiedAt = toValidDate(articleSchema.dateModified);
  const content = normalizeContentHtml(contentNode?.innerHTML || "");

  if (!title) {
    throw new Error(`Missing title for ${listItem.url}`);
  }

  if (!content) {
    throw new Error(`Missing article body for ${listItem.url}`);
  }

  const category = applyCategoryHeuristics({
    category: extractCategorySlug(documentRoot, articleSchema),
    title,
    content,
  });
  const createdAt = publishedAt || modifiedAt || null;
  const updatedAt = modifiedAt || publishedAt || null;
  const migratedPublishedAt = createdAt;

  return {
    title,
    slug: toNumericSlug(listItem.legacyPostId),
    category,
    excerpt: truncate(excerptSource),
    content,
    coverImageUrl: absoluteUrl(
      articleSchema.image || coverNode?.getAttribute("src") || "",
    ),
    author: sanitizeText(articleSchema.author?.name || ""),
    tags,
    status: "published",
    isFeatured: false,
    isActive: true,
    publishedAt: migratedPublishedAt,
    createdAt,
    updatedAt,
    seoTitle: title,
    seoDescription: truncate(excerptSource, 160),
    priority: 0,
    legacyUrl: listItem.url,
    legacyPostId: listItem.legacyPostId,
  };
};

const syncNewsTimestamps = async ({ newsId, createdAt, updatedAt }) => {
  const normalizedCreatedAt = toValidDate(createdAt);
  const normalizedUpdatedAt = toValidDate(updatedAt);

  if (!newsId || (!normalizedCreatedAt && !normalizedUpdatedAt)) {
    return;
  }

  const timestampUpdate = {};

  if (normalizedCreatedAt) {
    timestampUpdate.createdAt = normalizedCreatedAt;
  }

  if (normalizedUpdatedAt) {
    timestampUpdate.updatedAt = normalizedUpdatedAt;
  }

  await (await getDbResources()).models.news.collection.updateOne(
    { _id: newsId },
    { $set: timestampUpdate },
  );
};

const upsertNewsRecord = async (record) => {
  const { models } = await getDbResources();
  const coverImageFile = await upsertCoverImageFile({
    slug: record.slug,
    coverImageUrl: record.coverImageUrl,
  });
  const payload = {
    title: record.title,
    slug: record.slug,
    category: record.category,
    excerpt: record.excerpt,
    content: record.content,
    coverImage: coverImageFile?._id || null,
    author: record.author,
    tags: record.tags,
    status: record.status,
    isFeatured: record.isFeatured,
    isActive: record.isActive,
    publishedAt: record.publishedAt,
    seoTitle: record.seoTitle,
    seoDescription: record.seoDescription,
    priority: record.priority,
  };

  const existing = await models.news.findOne({ slug: record.slug }).select("_id");

  if (existing) {
    await models.news.updateOne({ _id: existing._id }, { $set: payload });
    await syncNewsTimestamps({
      newsId: existing._id,
      createdAt: record.createdAt,
      updatedAt: record.updatedAt,
    });
    return "updated";
  }

  const createdNews = await models.news.create(payload);
  await syncNewsTimestamps({
    newsId: createdNews._id,
    createdAt: record.createdAt,
    updatedAt: record.updatedAt,
  });
  return "created";
};

const collectLegacyPosts = async ({ startPage, endPage, limit, verbose }) => {
  const collected = [];

  for (let page = startPage; page <= endPage; page += 1) {
    const url = `${BLOG_URL}?page=${page}`;
    const html = await fetchHtml(url);
    const posts = extractListPagePosts(html);

    if (verbose) {
      console.log(
        JSON.stringify(
          {
            page,
            postsFound: posts.length,
          },
          null,
          2,
        ),
      );
    }

    for (const post of posts) {
      if (!collected.some((item) => item.legacyPostId === post.legacyPostId)) {
        collected.push(post);
      }

      if (limit && collected.length >= limit) {
        return collected;
      }
    }
  }

  return collected;
};

const run = async () => {
  const options = parseArgs();
  const legacyPosts = await collectLegacyPosts(options);
  const summary = {
    pages: {
      start: options.startPage,
      end: options.endPage,
    },
    dryRun: options.dryRun,
    discovered: legacyPosts.length,
    created: 0,
    updated: 0,
    failed: 0,
    failures: [],
    preview: [],
  };

  for (const [index, legacyPost] of legacyPosts.entries()) {
    try {
      const html = await fetchHtml(legacyPost.url);
      const record = extractArticleRecord(html, legacyPost);

      if (options.dryRun) {
        summary.preview.push({
          slug: record.slug,
          title: record.title,
          category: record.category,
          publishedAt: record.publishedAt,
          createdAt: record.createdAt,
          updatedAt: record.updatedAt,
          legacyUrl: record.legacyUrl,
        });
      } else {
        const result = await upsertNewsRecord(record);
        summary[result] += 1;
      }

      console.log(
        `[${index + 1}/${legacyPosts.length}] ${options.dryRun ? "parsed" : "imported"} ${record.slug} ${record.title}`,
      );
    } catch (error) {
      summary.failed += 1;
      summary.failures.push({
        legacyPostId: legacyPost.legacyPostId,
        url: legacyPost.url,
        message: error.message,
      });
      console.error(
        `[${index + 1}/${legacyPosts.length}] failed ${legacyPost.url}: ${error.message}`,
      );
    }
  }

  if (options.dryRun) {
    summary.preview = summary.preview.slice(0, 10);
  }

  console.log(JSON.stringify(summary, null, 2));
};

try {
  await run();
} catch (error) {
  console.error("Legacy news import failed:", error);
  process.exitCode = 1;
} finally {
  if (dbResourcesPromise) {
    const { mongoConnection } = await getDbResources();
    await mongoConnection.close();
  }
}
