import type { Context, Next } from "hono";
import { logError } from "@/lib/logger";
import { HotDealsService } from "@/v1/services/admin/hot-deals/hot-deals.service";
import { EDMService } from "@/v1/services/admin/edm/edm.service";

export async function activityLogMiddleware(ctx: Context, next: Next) {
  const method = ctx.req.method;
  const url = ctx.req.url;
  const userAgent = ctx.req.header("user-agent");
  const ipAddress = ctx.req.header("x-forwarded-for") || ctx.req.header("remote-addr");
  const referer = ctx.req.header("referer");

  const urlObj = new URL(url);
  const pathname = urlObj.pathname;
  const queryParams = Object.fromEntries(urlObj.searchParams);

  const logCategoryRequest = ctx.req.header("x-log-category") || "API";

  // Skip GET requests to activity-logs, session check, and SSE to avoid bloat
  if (
    method === "GET" || method === "POST" &&
    (pathname.includes("/admin/activity-logs") ||
      pathname.includes("/admin/console-user/me") ||
      pathname.includes("/internal/sse"))
  ) {
    return await next();
  }

  // Capture payload for mutating requests
  let payload: any = null;
  let beforePayload: any = null;
  const db = ctx.get("datastore");

  if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
    try {
      // Clone request so original is still readable by subsequent handlers
      const clone = ctx.req.raw.clone();
      if (clone.headers.get("content-type")?.includes("application/json")) {
        payload = await clone.json();
      }

      // Capture 'Before' state for Updates
      if (db && (method === "PUT" || method === "PATCH" || method === "DELETE")) {
        const campaignMatch = pathname.match(/\/v1\/admin-console\/notifications\/campaigns\/([^\/]+)/);
        if (campaignMatch) {
          const campaignId = campaignMatch[1];
          const existing = await db.selectFrom("campaigns" as any).selectAll().where("id", "=", campaignId).executeTakeFirst();
          if (existing) {
            beforePayload = existing;
          }
        }

        const hotDealMatch = pathname.match(/\/v1\/admin-console\/hot-deals\/([^\/]+)/);
        if (hotDealMatch) {
          const dealId = hotDealMatch[1];
          try {
            const result = await HotDealsService.getInstance().getDeal(db, dealId);
            if (result.success && result.data) {
              beforePayload = result.data;
            }
          } catch {
            // silently skip if file can't be read
          }
        }

        const edmMatch = pathname.match(/\/v1\/admin-console\/edm\/([^\/]+)/);
        if (edmMatch) {
          const templateId = edmMatch[1];
          try {
            const result = await EDMService.getInstance().getTemplate(db, templateId);
            if (result.success && result.data) {
              beforePayload = result.data;
            }
          } catch {
            // silently skip if the store or SendGrid is unreachable
          }
        }

        const campaignMappingMatch = pathname.match(
          /\/v1\/admin-console\/promo-code-campaigns\/([^/]+)$/,
        );
        if (campaignMappingMatch) {
          const id = campaignMappingMatch[1];
          try {
            const { sql } = await import("kysely");
            const existing = await db
              .selectFrom("promo_code_campaigns" as any)
              .selectAll()
              .where(sql<boolean>`id = ${id}::uuid` as any)
              .executeTakeFirst();
            if (existing) beforePayload = existing;
          } catch {
            // silently skip if id is not a uuid or table not present
          }
        }

        const detachMatch = pathname.match(
          /\/v1\/admin-console\/promo-code-campaigns\/([^/]+)\/promo-codes\/([^/]+)$/,
        );
        if (detachMatch && method === "DELETE") {
          const [, campaignId, promoCodeId] = detachMatch;
          try {
            const { sql } = await import("kysely");
            const existing = await db
              .selectFrom("promo_code_campaign_map" as any)
              .selectAll()
              .where(sql<boolean>`campaign_id = ${campaignId}::uuid` as any)
              .where(sql<boolean>`promo_code_id = ${promoCodeId}::uuid` as any)
              .executeTakeFirst();
            if (existing) beforePayload = existing;
          } catch {
            // silently skip
          }
        }
      }
    } catch (e) {
      // Silently skip if body can't be parsed
    }
  }

  // We need to wait for the response to get the status code and body
  await next();

  const adminEmail = ctx.get("adminEmail") || "anonymous";
  const statusCode = ctx.res.status;
  const entityId = ctx.get("entityId");

  // Try to capture response payload if it's JSON
  let responsePayload: any = null;
  if (ctx.res.headers.get("content-type")?.includes("application/json")) {
    try {
      // We must clone the response to read it, otherwise it might be consumed
      const resClone = ctx.res.clone();
      responsePayload = await resClone.json();
    } catch (e) {
      // Silently skip if response can't be parsed
    }
  }

  if (db) {
    try {
      let serializedPayload = null;
      if (payload) {
        try {
          serializedPayload = JSON.stringify(payload);
          // Max 2MB for payload just in case
          if (serializedPayload.length > 2 * 1024 * 1024) {
            serializedPayload = JSON.stringify({ error: "Payload too large to log" });
          }
        } catch (e) { }
      }

      let serializedBeforePayload = null;
      if (beforePayload) {
        try {
          serializedBeforePayload = JSON.stringify(beforePayload);
        } catch (e) { }
      }

      let serializedResponsePayload = null;
      if (responsePayload) {
        try {
          serializedResponsePayload = JSON.stringify(responsePayload);
          // Max 2MB for response just in case
          if (serializedResponsePayload.length > 2 * 1024 * 1024) {
            serializedResponsePayload = JSON.stringify({ error: "Response too large to log" });
          }
        } catch (e) { }
      }

      await db
        .insertInto("admin_activity_log" as any)
        .values({
          admin_email: adminEmail,
          action: `${method} ${pathname}`,
          method: method,
          url: url,
          status_code: statusCode,
          log_category: logCategoryRequest,
          ip_address: ipAddress || null,
          user_agent: userAgent || null,
          payload: serializedPayload,
          before_payload: serializedBeforePayload,
          response_payload: serializedResponsePayload,
          query_params: JSON.stringify(queryParams),
          origin_page: referer || null,
          metadata: entityId ? JSON.stringify({ entityId }) : null,
          performed_at: new Date(),
        })
        .execute();
    } catch (err) {
      // logError(`activityLogMiddleware: Failed to save log: ${err}`);
    }
  }
}

