import { Request, Response, NextFunction, RequestHandler } from "express";
import { Blogs } from "../models/Blogs";
import { Language } from "../models/Language";
import { RelatedBlogs } from "../models/RelatedBlogs";
import { QueryBuilder } from "objection";
import multer from "multer";
import { z } from "zod";
import path from "path";
import fs from "fs";
import { blogFormSchema } from "../schema/blogSchema";
import { generateContent } from "../scripts/content-generator";
import { pingPageChange } from "../services/indexingService";

// ----------------------------
// Multer Configuration
// ----------------------------

const isProduction = process.env.NODE_ENV === "production";
const baseDir = path.join(__dirname, "..", "..", "..");
const BLOG_UPLOAD_DIR = isProduction
  ? path.join(baseDir, "nytsolvers", "dist", "client", "images", "blogs")
  : path.join(baseDir, "project", "public", "images", "blogs");

const storage = multer.diskStorage({
  destination: (_req: Request, _file: Express.Multer.File, cb: (error: Error | null, destination: string) => void) => {
    if (!fs.existsSync(BLOG_UPLOAD_DIR)) {
      fs.mkdirSync(BLOG_UPLOAD_DIR, { recursive: true });
    }
    cb(null, BLOG_UPLOAD_DIR);
  },
  filename: (_req: Request, file: Express.Multer.File, cb: (error: Error | null, filename: string) => void) => {
    cb(null, file.originalname); // assumes UUID-based name from frontend
  },
});

const fileFilter = (
  _req: Request,
  file: Express.Multer.File,
  cb: multer.FileFilterCallback
) => {
  if (file.mimetype.startsWith("image/")) {
    cb(null, true);
  } else {
    cb(new Error("Only image files are allowed"));
  }
};

const upload = multer({ storage, fileFilter });
export const uploadMiddleware = upload.single("featured_image");

const parseListInput = (raw: any): any[] => {
  if (raw === undefined || raw === null || raw === "") return [];
  if (Array.isArray(raw)) {
    return raw.flatMap((item: any) => {
      if (typeof item === "string") {
        const trimmed = item.trim();
        if (!trimmed) return [];
        try {
          const parsed = JSON.parse(trimmed);
          if (Array.isArray(parsed)) return parsed;
        } catch {}
        return trimmed.includes(",") ? trimmed.split(",").map((s) => s.trim()) : [trimmed];
      }
      return [item];
    });
  }
  if (typeof raw === "string") {
    const trimmed = raw.trim();
    if (!trimmed) return [];
    try {
      const parsed = JSON.parse(trimmed);
      if (Array.isArray(parsed)) return parsed;
    } catch {}
    return trimmed.split(",").map((s) => s.trim());
  }
  return [raw];
};

const getRelatedBlogIds = (body: any): number[] => {
  const raw = body.related_blogs !== undefined ? body.related_blogs : body["related_blogs[]"];
  const ids = parseListInput(raw)
    .map((item: any) => (typeof item === "object" && item !== null ? Number(item.id) : Number(item)))
    .filter((id: number) => Number.isInteger(id) && id > 0);
  return Array.from(new Set(ids));
};

const getRelatedTools = (body: any): string[] => {
  const raw = body.related_tools !== undefined ? body.related_tools : body["related_tools[]"];
  const slugs = parseListInput(raw)
    .map((item: any) =>
      (typeof item === "object" && item !== null ? String(item.slug || item.href || "") : String(item || ""))
        .trim()
        .replace(/^\/+|\/+$/g, "")
    )
    .filter(Boolean);
  return Array.from(new Set(slugs));
};

const syncRelatedBlogs = async (blogId: number, rawIds: number[]) => {
  const uniqueIds = Array.from(new Set(rawIds)).filter(
    (rid) => Number.isInteger(rid) && rid > 0 && rid !== blogId
  );

  const knex = RelatedBlogs.knex();
  await knex("related_blogs").where("blog_id", blogId).del();

  if (uniqueIds.length > 0) {
    const validBlogs = await Blogs.query().whereIn("id", uniqueIds).select("id");
    const validIdSet = new Set(validBlogs.map((b: any) => Number(b.id)));
    const rows = uniqueIds
      .filter((rid) => validIdSet.has(rid))
      .map((relatedId) => ({
        blog_id: blogId,
        related_blog_id: relatedId,
      }));
    if (rows.length > 0) {
      await knex("related_blogs").insert(rows);
    }
  }
};


// ----------------------------
// Blog Controller
// ----------------------------

export class BlogController {
  private sendError(res: Response, status: number, message: string, errors?: any): void { // Added errors param
    res.status(status).json({ message, errors }); // Include errors in response
  }

  private sendSuccess(res: Response, message: string, data?: any): void {
    res.status(200).json({ success: true, message, data });
  }

  show: RequestHandler = async (req: Request, res: Response, next: NextFunction) => {
    try {
      const blogs = await Blogs.query()
        .select(
          "blogs.id",
          "blogs.title",
          "blogs.featured_image",
          "blogs.is_active",
          "blogs.lang",
          "blogs.related_tools"
        )
        .withGraphFetched("[language(selectLang), relatedBlogs(selectRelated)]")
        .modifiers({
          selectLang: (builder: QueryBuilder<Language>) => {
            builder.select("id", "name");
          },
          selectRelated: (builder: QueryBuilder<Blogs>) => {
            builder.select("blogs.id", "blogs.title");
          },
        });

      const result = blogs.map((page: Blogs) => ({
        ...page,
        language: page.language?.name ?? null,
        related_blogs:
          page.relatedBlogs?.map((rel) => ({
            id: rel.id,
            title: rel.title,
          })) ?? [],
        related_tools: page.related_tools ? (typeof page.related_tools === "string" ? (() => { try { return JSON.parse(page.related_tools); } catch { return []; } })() : page.related_tools) : [],
      }));

      return this.sendSuccess(res, "Blogs fetched successfully", result);
    } catch (error: any) { // Explicitly type error
      console.error("Error fetching blogs:", error);
      next(error);
    }
  };

  add: RequestHandler = async (req: Request, res: Response, next: NextFunction) => {
    try {
      const { file } = req;

      const formValues = {
        ...req.body,
        featured_image: file ? `images/blogs/${file.filename}` : "",
      };

      const parsedData = blogFormSchema.parse(formValues);

      // ✅ Validate language
      const language = await Language.query().findOne(
        "lang_code",
        parsedData.lang
      );
      if (!language) {
        if (file) this.deleteUploadedFile(file.filename);
        this.sendError(res, 400, `Invalid language code: ${parsedData.lang}`);
        return;
      }

      // ✅ Create blog entry
      const newBlog = await Blogs.query().insert({
        title: parsedData.title,
        description: parsedData.description,
        slug: parsedData.slug,
        meta_title: parsedData.meta_title,
        meta_description: parsedData.meta_description,
        content: parsedData.content,
        featured_image: formValues.featured_image,
        is_active: parsedData.is_active === "1",
        noindex: parsedData.noindex === "1" || parsedData.noindex === true,
        lang: language.id,
        related_tools: JSON.stringify(getRelatedTools(req.body)),
        page_head_tag: parsedData.page_head_tag,
        before_closing_body_tag: parsedData.before_closing_body_tag,
        canonical: parsedData.canonical,
        updated_at: new Date().toISOString(),
      });

      // ✅ Related blogs parsing & sync
      const relatedBlogIds = getRelatedBlogIds(req.body);
      await syncRelatedBlogs(Number(newBlog.id), relatedBlogIds);

      await generateContent().catch((e) => console.error("Error auto-generating content after blog create:", e));

      if (newBlog.is_active && !newBlog.noindex) {
        pingPageChange(newBlog.slug, "URL_UPDATED", true).catch((e) =>
          console.error("Error pinging Google Indexing API after blog create:", e)
        );
      }

      this.sendSuccess(res, "Blog page created successfully", {
        ...newBlog,
        related_blogs: relatedBlogIds,
        related_tools: getRelatedTools(req.body),
      });
    } catch (error: any) { // Explicitly type error
      if (req.file) this.deleteUploadedFile(req.file.filename);

      if (error instanceof z.ZodError) {
        this.sendError(res, 400, "Validation failed", error.errors); // Pass errors for more detail
        return;
      }

      console.error("Error creating blog:", error);
      this.sendError(res, 500, "Failed to create blog page", error instanceof Error ? error.message : "Unknown error");
    }
  };

  get: RequestHandler = async (req: Request, res: Response) => {
    try {
      const blog = await Blogs.query()
        .findById(Number(req.params.id)) // Ensure id is number
        .withGraphFetched("[language, relatedBlogs(selectBasic)]")
        .modifiers({
          selectBasic: (builder: QueryBuilder<Blogs>) => {
            builder.select("blogs.id", "blogs.title");
          },
        });

      if (!blog) {
        this.sendError(res, 404, "Blog not found");
        return;
      }

      let relatedIds: number[] = blog.relatedBlogs?.map((rb: any) => Number(rb.id)).filter(Boolean) ?? [];
      if (relatedIds.length === 0) {
        try {
          const pivotRows = await RelatedBlogs.knex()("related_blogs")
            .where("blog_id", blog.id)
            .select("related_blog_id");
          relatedIds = pivotRows.map((r: any) => Number(r.related_blog_id)).filter(Boolean);
        } catch {}
      }

      const relatedTools = parseListInput(blog.related_tools)
        .map((s: any) => String(s || "").trim().replace(/^\/+|\/+$/g, ""))
        .filter(Boolean);

      this.sendSuccess(res, "Blog fetched successfully", {
        ...blog,
        lang: blog.language?.lang_code ?? null,
        related_blogs: relatedIds,
        related_tools: relatedTools,
        page_head_tag: blog.page_head_tag ?? null,
        before_closing_body_tag: blog.before_closing_body_tag ?? null,
        featured_image: blog.featured_image ?? null,
        canonical: blog.canonical ?? null,
        noindex: blog.noindex ?? false,
      });
    } catch (error: any) { // Explicitly type error
      console.error("Error fetching blog:", error);
      this.sendError(res, 500, "Failed to fetch blog page");
    }
  };

  edit: RequestHandler = async (req: Request, res: Response) => {
    const blogId = Number(req.params.id);

    try {
      const existingBlog = await Blogs.query().findById(blogId);
      if (!existingBlog) {
        this.sendError(res, 404, "Blog not found");
        return;
      }

      const { file } = req;

      // Determine the featured image path
      let featured_image_path = existingBlog.featured_image;
      if (file) {
        featured_image_path = `images/blogs/${file.filename}`;
      } else if (req.body.featured_image === '') {
        featured_image_path = '';
      }

      const formValues = {
        ...req.body,
        featured_image: featured_image_path,
      };

      const parsedData = blogFormSchema.parse(formValues);

      const language = await Language.query().findOne(
        "lang_code",
        parsedData.lang
      );
      if (!language) {
        if (file) this.deleteUploadedFile(file.filename);
        this.sendError(res, 400, `Invalid language code: ${parsedData.lang}`);
        return;
      }

      const relatedTools = getRelatedTools(req.body);
      const relatedBlogIds = getRelatedBlogIds(req.body);

      // Update the blog itself
      const updatedBlog = await Blogs.query().patchAndFetchById(blogId, {
        title: parsedData.title,
        description: parsedData.description,
        slug: parsedData.slug,
        meta_title: parsedData.meta_title,
        meta_description: parsedData.meta_description,
        content: parsedData.content,
        featured_image: featured_image_path, // Use the correct path
        is_active: parsedData.is_active === "1",
        noindex: parsedData.noindex === "1" || parsedData.noindex === true,
        lang: language.id,
        related_tools: JSON.stringify(relatedTools),
        page_head_tag: parsedData.page_head_tag,
        before_closing_body_tag: parsedData.before_closing_body_tag,
        canonical: parsedData.canonical,
        updated_at: new Date().toISOString(),
      });

      // 👇 Update related blogs BEFORE regenerating static content so page.json gets latest relations
      await syncRelatedBlogs(blogId, relatedBlogIds);

      await generateContent().catch((e) => console.error("Error auto-generating content after blog update:", e));

      // Trigger Google Indexing API ping
      if (existingBlog.slug && existingBlog.slug !== updatedBlog.slug) {
        pingPageChange(existingBlog.slug, "URL_DELETED", true).catch((e) =>
          console.error("Error pinging Google Indexing API after blog slug rename (delete old):", e)
        );
      }

      if (updatedBlog.is_active && !updatedBlog.noindex) {
        pingPageChange(updatedBlog.slug, "URL_UPDATED", true).catch((e) =>
          console.error("Error pinging Google Indexing API after blog update:", e)
        );
      } else {
        pingPageChange(updatedBlog.slug, "URL_DELETED", true).catch((e) =>
          console.error("Error pinging Google Indexing API after blog deactivation:", e)
        );
      }

      // Delete old image if a new one was uploaded or if the image was removed
      if (file && existingBlog.featured_image && path.basename(existingBlog.featured_image) !== file.filename) {
        this.deleteUploadedFile(path.basename(existingBlog.featured_image));
      } else if (req.body.featured_image === '' && existingBlog.featured_image) {
        this.deleteUploadedFile(path.basename(existingBlog.featured_image));
      }

      this.sendSuccess(res, "Blog updated successfully", {
        ...updatedBlog,
        related_blogs: relatedBlogIds,
        related_tools: relatedTools,
      });
      return;
    } catch (error: any) { // Explicitly type error
      if (req.file) this.deleteUploadedFile(req.file.filename);

      if (error instanceof z.ZodError) {
        this.sendError(res, 400, "Validation failed", error.errors);
        return;
      }

      console.error("Error updating blog:", error);
      this.sendError(res, 500, error?.message || "Failed to update blog");
    }
  };

  remove: RequestHandler = async (
    req: Request,
    res: Response,
    next: NextFunction
  ): Promise<void> => {
    try {
      const { id } = req.params;

      if (!id) {
        this.sendError(res, 400, "Missing blog page ID");
        return;
      }

      const blogs = await Blogs.query().findById(Number(id)); // Ensure id is number
      if (!blogs) {
        this.sendError(res, 404, "Blog page not found");
        return;
      }

      await Blogs.query().deleteById(Number(id));

      generateContent().catch((e) => console.error("Error auto-generating content after blog delete:", e)); // Ensure id is number

      if (blogs.slug) {
        pingPageChange(blogs.slug, "URL_DELETED", true).catch((e) =>
          console.error("Error pinging Google Indexing API after blog delete:", e)
        );
      }

      this.sendSuccess(res, "Blog page deleted successfully");
    } catch (error: any) { // Explicitly type error
      console.error("Error deleting blog page:", error);
      this.sendError(res, 500, "Failed to delete blog page");
    }
  };

  getBlog: RequestHandler = async (req: Request, res: Response) => {
    try {
      const { slug } = req.query;

      console.log("🔍 Fetching blog with slug:", slug);

      if (!slug || typeof slug !== "string") {
        this.sendError(res, 400, "Missing or invalid slug parameter");
        return;
      }

      const cleanSlug = slug.trim().replace(/^\/+|\/+$/g, "");

      const blog = await Blogs.query()
        .where((builder: any) => {
          builder.where("slug", cleanSlug)
            .orWhere("slug", `${cleanSlug} `)
            .orWhere("slug", slug);
        })
        .andWhere("is_active", true)
        .withGraphFetched("relatedBlogs")
        .modifyGraph("relatedBlogs", (builder: QueryBuilder<Blogs>) => {
          builder.select(
            "blogs.id as id",
            "blogs.title as title",
            "blogs.description as description",
            "blogs.slug as slug",
            "blogs.featured_image as featured_image",
            "blogs.created_at as created_at"
          );
        })
        .first();

      if (!blog) {
        console.warn("⚠️ Blog not found for slug:", slug);
        this.sendError(res, 404, "Blog not found");
        return;
      }

      this.sendSuccess(res, "Blog fetched successfully", { // Using sendSuccess for consistency
        id: blog.id,
        title: blog.title,
        slug: blog.slug,
        description: blog.description,
        meta_title: blog.meta_title,
        meta_description: blog.meta_description,
        content: blog.content,
        featured_image: blog.featured_image,
        related_blogs: blog.relatedBlogs ?? [],
        related_tools: parseListInput(blog.related_tools)
          .map((s: any) => String(s || "").trim().replace(/^\/+|\/+$/g, ""))
          .filter(Boolean),
        created_at: blog.created_at,
        page_head_tag: blog.page_head_tag,
        before_closing_body_tag: blog.before_closing_body_tag,
        noindex: blog.noindex,
      });
    } catch (err: any) { // Explicitly type error
      console.error("❌ Failed to fetch blog:", err);
      this.sendError(res, 500, "Internal server error");
    }
  };

  getBlogs: RequestHandler = async (req: Request, res: Response) => {
    try {
      const blogs = await Blogs.query()
        .where("is_active", true)
        .orderBy("created_at", "asc");

      const responseData = blogs.map((blog: Blogs) => ({ // Explicitly type blog
        id: blog.id,
        title: blog.title,
        slug: blog.slug,
        description: blog.description,
        featured_image: blog.featured_image,
        content: blog.content,
        meta_title: blog.meta_title,
        meta_description: blog.meta_description,
        created_at: blog.created_at,
      }));

      this.sendSuccess(res, "Blogs fetched successfully", responseData);
    } catch (err: any) { // Explicitly type error
      console.error("❌ Failed to fetch blogs:", err);
      this.sendError(res, 500, "Internal server error");
    }
  };

  private deleteUploadedFile(filename: string) {
    const fullPath = path.join(BLOG_UPLOAD_DIR, filename);
    if (fs.existsSync(fullPath)) {
      fs.unlinkSync(fullPath);
    }
  }
}
