import type { Context } from "hono";
import { error, success } from "@/lib/response";
import { logError } from "@/lib/logger";

export const MemberOfferController = (ctx: Context) => {
  const db = ctx.get("datastore");
  const memberDb = ctx.get("memberDatastore");

  return {
    async ListMemberOffers() {
      try {
        const q = ctx.req.query();
        const page = Math.max(1, parseInt(q.page ?? "1", 10));
        const limit = Math.min(100, Math.max(1, parseInt(q.limit ?? "20", 10)));
        const offset = (page - 1) * limit;
        const search = q.search?.trim() ?? "";
        const offerType = q.offer_type?.trim() ?? "";
        const resortId = q.resort_id?.trim() ?? "";
        const publishMode = q.publish_mode?.trim() ?? "";

        let query = db
          .selectFrom("member_offers as mo" as any)
          .leftJoin("resorts as r" as any, "r.id" as any, "mo.resort_id" as any)
          .select([
            "mo.id" as any,
            "mo.name" as any,
            "mo.offer_type" as any,
            "mo.resort_id" as any,
            "r.name as resort_name" as any,
            "mo.is_active" as any,
            "mo.booking_period_start" as any,
            "mo.booking_period_end" as any,
            "mo.travel_period_start" as any,
            "mo.travel_period_end" as any,
            "mo.publish_mode" as any,
            "mo.scheduled_publish_at" as any,
            "mo.published_at" as any,
            "mo.created_at" as any,
            "mo.updated_at" as any,
          ])
          .orderBy("mo.created_at", "desc");

        if (search) {
          query = query.where("mo.name", "ilike", `%${search}%`);
        }
        if (offerType) {
          query = query.where("mo.offer_type", "=", offerType);
        }
        if (resortId) {
          query = query.where("mo.resort_id", "=", resortId);
        }
        if (publishMode) {
          query = query.where("mo.publish_mode", "=", publishMode);
        }

        const items = await query.limit(limit).offset(offset).execute();

        let countQuery = db
          .selectFrom("member_offers" as any)
          .select((eb: any) => eb.fn.count("id").as("count"));
        if (search) countQuery = countQuery.where("name", "ilike", `%${search}%`);
        if (offerType) countQuery = countQuery.where("offer_type", "=", offerType);
        if (resortId) countQuery = countQuery.where("resort_id", "=", resortId);
        if (publishMode) countQuery = countQuery.where("publish_mode", "=", publishMode);

        const countResult = await countQuery.executeTakeFirst();
        const total = Number(countResult?.count ?? 0);

        return success(ctx, { items, total, page, limit });
      } catch (err: any) {
        logError("[MemberOffers] ListMemberOffers:", err);
        return error(ctx, err?.message ?? "Failed to list member offers", 500);
      }
    },

    async GetMemberOffer() {
      try {
        const { id } = ctx.req.param() as { id: string };

        const offer = await db
          .selectFrom("member_offers" as any)
          .selectAll()
          .where("id", "=", id)
          .executeTakeFirst();

        if (!offer) return error(ctx, "Member offer not found", 404);

        const unitOffers = await db
          .selectFrom("member_offer_units as mou" as any)
          .leftJoin("resort_units as ru" as any, "ru.id" as any, "mou.resort_unit_id" as any)
          .select([
            "mou.id" as any,
            "mou.member_offer_id" as any,
            "mou.resort_unit_id" as any,
            "ru.unit_name" as any,
            "ru.unit_vp_code" as any,
            "ru.unit_type" as any,
            "mou.is_admin_override" as any,
            "mou.offer_value_type" as any,
            "mou.offer_value" as any,
            "mou.minimum_nights" as any,
            "mou.maximum_nights_per_booking" as any,
            "mou.maximum_units_per_membership" as any,
          ])
          .where("mou.member_offer_id", "=", id)
          .execute();

        return success(ctx, { ...offer, unit_offers: unitOffers });
      } catch (err: any) {
        logError("[MemberOffers] GetMemberOffer:", err);
        return error(ctx, err?.message ?? "Failed to get member offer", 500);
      }
    },

    async CreateMemberOffer() {
      try {
        const body = await ctx.req.json();

        if (!body.name?.trim()) return error(ctx, "name is required", 400);
        if (!body.offer_type) return error(ctx, "offer_type is required", 400);
        if (!body.resort_id) return error(ctx, "resort_id is required", 400);
        if (!body.booking_period_start) return error(ctx, "booking_period_start is required", 400);
        if (!body.booking_period_end) return error(ctx, "booking_period_end is required", 400);
        if (!body.travel_period_start) return error(ctx, "travel_period_start is required", 400);
        if (!body.travel_period_end) return error(ctx, "travel_period_end is required", 400);

        const adminEmail = ctx.get("email") ?? ctx.get("adminEmail") ?? null;

        const offer = await db
          .insertInto("member_offers" as any)
          .values({
            name: body.name.trim(),
            offer_type: body.offer_type,
            description: body.description ?? null,
            resort_id: body.resort_id,
            is_active: body.is_active ?? true,
            is_cancellable: body.is_cancellable ?? true,
            booking_period_start: body.booking_period_start,
            booking_period_end: body.booking_period_end,
            travel_period_start: body.travel_period_start,
            travel_period_end: body.travel_period_end,
            blocked_account_types: body.blocked_account_types ?? [],
            blocked_account_statuses: body.blocked_account_statuses ?? [],
            blocked_clubs: body.blocked_clubs ?? [],
            allowed_user_types: body.allowed_user_types ?? ["LEGAL_OWNER", "FAMILY_MEMBER", "GUEST"],
            inclusions: body.inclusions ?? null,
            terms_and_conditions: body.terms_and_conditions ?? null,
            confirmation_edm_template_ids: body.confirmation_edm_template_ids ?? [],
            cancellation_edm_template_ids: body.cancellation_edm_template_ids ?? [],
            publish_mode: body.publish_mode ?? "DRAFT",
            scheduled_publish_at: body.scheduled_publish_at ?? null,
            published_at: body.publish_mode === "PUBLISHED" ? new Date() : null,
            raw_config: body.raw_config ?? null,
            created_by: adminEmail,
            updated_by: adminEmail,
          })
          .returningAll()
          .executeTakeFirstOrThrow();

        if (Array.isArray(body.unit_offers) && body.unit_offers.length > 0) {
          await db
            .insertInto("member_offer_units" as any)
            .values(
              body.unit_offers.map((u: any) => ({
                member_offer_id: offer.id,
                resort_unit_id: u.resort_unit_id,
                is_admin_override: u.is_admin_override ?? false,
                offer_value_type: u.offer_value_type ?? null,
                offer_value: u.offer_value ?? null,
                minimum_nights: u.minimum_nights ?? null,
                maximum_nights_per_booking: u.maximum_nights_per_booking ?? null,
                maximum_units_per_membership: u.maximum_units_per_membership ?? null,
              })),
            )
            .execute();
        }

        const unitOffers = await db
          .selectFrom("member_offer_units" as any)
          .selectAll()
          .where("member_offer_id", "=", offer.id)
          .execute();

        return success(ctx, { ...offer, unit_offers: unitOffers }, "Member offer created", 201);
      } catch (err: any) {
        logError("[MemberOffers] CreateMemberOffer:", err);
        return error(ctx, err?.message ?? "Failed to create member offer", 500);
      }
    },

    async UpdateMemberOffer() {
      try {
        const { id } = ctx.req.param() as { id: string };
        const body = await ctx.req.json();

        const existing = await db
          .selectFrom("member_offers" as any)
          .selectAll()
          .where("id", "=", id)
          .executeTakeFirst();

        if (!existing) return error(ctx, "Member offer not found", 404);

        if (body.name !== undefined && !body.name?.trim()) {
          return error(ctx, "name cannot be empty", 400);
        }

        const adminEmail = ctx.get("email") ?? ctx.get("adminEmail") ?? null;

        // Role-based editing restriction check
        if (adminEmail) {
          const caller = await db
            .selectFrom("console_users" as any)
            .select(["id", "is_super_admin"])
            .where("email", "=", adminEmail)
            .executeTakeFirst();

          const isSuperAdmin = caller?.is_super_admin ?? false;

          if (!isSuperAdmin) {
            const workflowDb = db;
            const task = await workflowDb
              .selectFrom("workflow_tasks" as any)
              .select(["assignor_id"])
              .where("entity_type", "=", "MEMBER_OFFER")
              .where("entity_id", "=", id)
              .executeTakeFirst();

            if (task && task.assignor_id && task.assignor_id !== caller?.id) {
              const configFields = [
                "offer_type",
                "resort_id",
                "booking_period_start",
                "booking_period_end",
                "travel_period_start",
                "travel_period_end",
                "blocked_account_types",
                "blocked_account_statuses",
                "blocked_clubs",
                "allowed_user_types",
                "confirmation_edm_template_ids",
                "cancellation_edm_template_ids",
                "is_active",
                "is_cancellable",
                "publish_mode",
                "scheduled_publish_at",
                "raw_config",
              ];

              const isDifferent = (val1: any, val2: any) => {
                if (val1 === val2) return false;
                if (val1 instanceof Date && val2 instanceof Date) {
                  return val1.getTime() !== val2.getTime();
                }
                if (typeof val1 === "string" && val2 instanceof Date) {
                  return new Date(val1).getTime() !== val2.getTime();
                }
                if (val2 instanceof Date && typeof val1 === "string") {
                  return new Date(val1).getTime() !== val2.getTime();
                }
                if (typeof val1 === "object" || typeof val2 === "object") {
                  return JSON.stringify(val1) !== JSON.stringify(val2);
                }
                return val1 !== val2;
              };

              let hasConfigChanges = false;
              for (const f of configFields) {
                if (body[f] !== undefined && isDifferent(body[f], (existing as any)[f])) {
                  hasConfigChanges = true;
                  break;
                }
              }

              if (!hasConfigChanges && body.unit_offers !== undefined) {
                const existingUnitOffers = await db
                  .selectFrom("member_offer_units" as any)
                  .selectAll()
                  .where("member_offer_id", "=", id)
                  .execute();

                if (body.unit_offers.length !== existingUnitOffers.length) {
                  hasConfigChanges = true;
                } else {
                  for (const u of body.unit_offers) {
                    const match = existingUnitOffers.find((eo: any) => eo.resort_unit_id === u.resort_unit_id);
                    if (!match) {
                      hasConfigChanges = true;
                      break;
                    }
                    if (
                      match.is_admin_override !== (u.is_admin_override ?? false) ||
                      match.offer_value_type !== (u.offer_value_type ?? null) ||
                      match.offer_value !== (u.offer_value ?? null) ||
                      match.minimum_nights !== (u.minimum_nights ?? null) ||
                      match.maximum_nights_per_booking !== (u.maximum_nights_per_booking ?? null) ||
                      match.maximum_units_per_membership !== (u.maximum_units_per_membership ?? null)
                    ) {
                      hasConfigChanges = true;
                      break;
                    }
                  }
                }
              }

              if (hasConfigChanges) {
                return error(
                  ctx,
                  "Content writers can only edit content fields (name, description, inclusions, terms and conditions). Changing configuration fields is not allowed.",
                  403
                );
              }
            }
          }
        }

        const validatePublish = async (offerId: string, currentBody: any, existingOffer: any) => {
          const targetPublishMode = currentBody.publish_mode ?? existingOffer.publish_mode;
          if (targetPublishMode === "PUBLISHED" || targetPublishMode === "SCHEDULED") {
            const unitOffers = currentBody.unit_offers !== undefined
              ? currentBody.unit_offers
              : await db.selectFrom("member_offer_units" as any).selectAll().where("member_offer_id", "=", offerId).execute();

            if (unitOffers.length === 0) throw new Error("Cannot publish: No accommodations or offer values have been configured.");
            for (const u of unitOffers) {
              if (u.offer_value === null || u.offer_value === undefined || String(u.offer_value).trim() === "") {
                throw new Error("Cannot publish: Offer values are missing for some accommodations.");
              }
            }
            const adminEmail = ctx.get("email") ?? ctx.get("adminEmail") ?? null;
            if (adminEmail) {
              const caller = await db.selectFrom("console_users" as any).select(["is_super_admin"]).where("email", "=", adminEmail).executeTakeFirst();
              if (!caller?.is_super_admin) {
                const workflowDb = db;
                const task = await workflowDb.selectFrom("workflow_tasks" as any).select(["status"]).where("entity_type", "=", "MEMBER_OFFER").where("entity_id", "=", offerId).executeTakeFirst();
                if (!task || task.status !== "VERIFIED") {
                  throw new Error("Cannot publish: The content must be reviewed and approved (status 'Verified') in the workflow sidebar before publishing.");
                }
              }
            }
          }
        };

        if (body.publish_mode) {
          await validatePublish(id, body, existing);
        }

        const updates: Record<string, any> = { updated_at: new Date(), updated_by: adminEmail };
        const scalarFields = [
          "name", "description", "resort_id", "is_active", "is_cancellable",
          "booking_period_start", "booking_period_end", "travel_period_start", "travel_period_end",
          "blocked_account_types", "blocked_account_statuses", "blocked_clubs", "allowed_user_types",
          "inclusions", "terms_and_conditions",
          "confirmation_edm_template_ids", "cancellation_edm_template_ids",
          "publish_mode", "scheduled_publish_at", "raw_config",
        ];
        for (const f of scalarFields) {
          if (body[f] !== undefined) updates[f] = body[f];
        }

        if (body.publish_mode === "PUBLISHED" && existing.publish_mode !== "PUBLISHED") {
          updates.published_at = new Date();
        }

        const offer = await db
          .updateTable("member_offers" as any)
          .set(updates)
          .where("id", "=", id)
          .returningAll()
          .executeTakeFirstOrThrow();

        if (Array.isArray(body.unit_offers)) {
          await db.deleteFrom("member_offer_units" as any).where("member_offer_id", "=", id).execute();
          if (body.unit_offers.length > 0) {
            await db
              .insertInto("member_offer_units" as any)
              .values(
                body.unit_offers.map((u: any) => ({
                  member_offer_id: id,
                  resort_unit_id: u.resort_unit_id,
                  is_admin_override: u.is_admin_override ?? false,
                  offer_value_type: u.offer_value_type ?? null,
                  offer_value: u.offer_value ?? null,
                  minimum_nights: u.minimum_nights ?? null,
                  maximum_nights_per_booking: u.maximum_nights_per_booking ?? null,
                  maximum_units_per_membership: u.maximum_units_per_membership ?? null,
                })),
              )
              .execute();
          }
        }

        const unitOffers = await db
          .selectFrom("member_offer_units as mou" as any)
          .leftJoin("resort_units as ru" as any, "ru.id" as any, "mou.resort_unit_id" as any)
          .select([
            "mou.id" as any,
            "mou.member_offer_id" as any,
            "mou.resort_unit_id" as any,
            "ru.unit_name" as any,
            "ru.unit_vp_code" as any,
            "ru.unit_type" as any,
            "mou.is_admin_override" as any,
            "mou.offer_value_type" as any,
            "mou.offer_value" as any,
            "mou.minimum_nights" as any,
            "mou.maximum_nights_per_booking" as any,
            "mou.maximum_units_per_membership" as any,
          ])
          .where("mou.member_offer_id", "=", id)
          .execute();

        return success(ctx, { ...offer, unit_offers: unitOffers }, "Member offer updated");
      } catch (err: any) {
        logError("[MemberOffers] UpdateMemberOffer:", err);
        return error(ctx, err?.message ?? "Failed to update member offer", 500);
      }
    },

    async DeleteMemberOffer() {
      try {
        const { id } = ctx.req.param() as { id: string };

        const existing = await db
          .selectFrom("member_offers" as any)
          .select(["id", "publish_mode"])
          .where("id", "=", id)
          .executeTakeFirst();

        if (!existing) return error(ctx, "Member offer not found", 404);
        if (existing.publish_mode !== "DRAFT") {
          return error(ctx, "Only draft offers can be deleted. Archive published offers instead.", 400);
        }

        await db.deleteFrom("member_offers" as any).where("id", "=", id).execute();

        return success(ctx, null, "Member offer deleted");
      } catch (err: any) {
        logError("[MemberOffers] DeleteMemberOffer:", err);
        return error(ctx, err?.message ?? "Failed to delete member offer", 500);
      }
    },

    async PublishMemberOffer() {
      try {
        const { id } = ctx.req.param() as { id: string };

        const existing = await db
          .selectFrom("member_offers" as any)
          .select(["id", "publish_mode"])
          .where("id", "=", id)
          .executeTakeFirst();

        if (!existing) return error(ctx, "Member offer not found", 404);
        if (existing.publish_mode === "ARCHIVED") {
          return error(ctx, "Archived offers cannot be re-published", 400);
        }

        const validatePublish = async (offerId: string) => {
          const unitOffers = await db.selectFrom("member_offer_units" as any).selectAll().where("member_offer_id", "=", offerId).execute();
          if (unitOffers.length === 0) throw new Error("Cannot publish: No accommodations or offer values have been configured.");
          for (const u of unitOffers) {
            if (u.offer_value === null || u.offer_value === undefined || String(u.offer_value).trim() === "") {
              throw new Error("Cannot publish: Offer values are missing for some accommodations.");
            }
          }
          const adminEmail = ctx.get("email") ?? ctx.get("adminEmail") ?? null;
          if (adminEmail) {
            const caller = await db.selectFrom("console_users" as any).select(["is_super_admin"]).where("email", "=", adminEmail).executeTakeFirst();
            if (!caller?.is_super_admin) {
              const workflowDb = db;
              const task = await workflowDb.selectFrom("workflow_tasks" as any).select(["status"]).where("entity_type", "=", "MEMBER_OFFER").where("entity_id", "=", offerId).executeTakeFirst();
              if (!task || task.status !== "VERIFIED") {
                throw new Error("Cannot publish: The content must be reviewed and approved (status 'Verified') in the workflow sidebar before publishing.");
              }
            }
          }
        };

        await validatePublish(id);

        const adminEmail = ctx.get("email") ?? ctx.get("adminEmail") ?? null;

        const offer = await db
          .updateTable("member_offers" as any)
          .set({
            publish_mode: "PUBLISHED",
            published_at: new Date(),
            updated_at: new Date(),
            updated_by: adminEmail,
          })
          .where("id", "=", id)
          .returningAll()
          .executeTakeFirstOrThrow();

        return success(ctx, offer, "Member offer published");
      } catch (err: any) {
        logError("[MemberOffers] PublishMemberOffer:", err);
        return error(ctx, err?.message ?? "Failed to publish member offer", 500);
      }
    },

    async ArchiveMemberOffer() {
      try {
        const { id } = ctx.req.param() as { id: string };

        const existing = await db
          .selectFrom("member_offers" as any)
          .select(["id", "publish_mode"])
          .where("id", "=", id)
          .executeTakeFirst();

        if (!existing) return error(ctx, "Member offer not found", 404);

        const adminEmail = ctx.get("email") ?? ctx.get("adminEmail") ?? null;

        const offer = await db
          .updateTable("member_offers" as any)
          .set({
            publish_mode: "ARCHIVED",
            updated_at: new Date(),
            updated_by: adminEmail,
          })
          .where("id", "=", id)
          .returningAll()
          .executeTakeFirstOrThrow();

        return success(ctx, offer, "Member offer archived");
      } catch (err: any) {
        logError("[MemberOffers] ArchiveMemberOffer:", err);
        return error(ctx, err?.message ?? "Failed to archive member offer", 500);
      }
    },

    async DuplicateMemberOffer() {
      try {
        const { id } = ctx.req.param() as { id: string };

        const existing = await db
          .selectFrom("member_offers" as any)
          .selectAll()
          .where("id", "=", id)
          .executeTakeFirst();

        if (!existing) return error(ctx, "Member offer not found", 404);

        const adminEmail = ctx.get("email") ?? ctx.get("adminEmail") ?? null;

        const copy = await db
          .insertInto("member_offers" as any)
          .values({
            name: `${existing.name} (Copy)`,
            offer_type: existing.offer_type,
            description: existing.description,
            resort_id: existing.resort_id,
            is_active: existing.is_active,
            is_cancellable: existing.is_cancellable,
            booking_period_start: existing.booking_period_start,
            booking_period_end: existing.booking_period_end,
            travel_period_start: existing.travel_period_start,
            travel_period_end: existing.travel_period_end,
            blocked_account_types: existing.blocked_account_types,
            blocked_account_statuses: existing.blocked_account_statuses,
            blocked_clubs: existing.blocked_clubs,
            allowed_user_types: existing.allowed_user_types,
            inclusions: existing.inclusions,
            terms_and_conditions: existing.terms_and_conditions,
            confirmation_edm_template_ids: existing.confirmation_edm_template_ids,
            cancellation_edm_template_ids: existing.cancellation_edm_template_ids,
            publish_mode: "DRAFT",
            scheduled_publish_at: null,
            published_at: null,
            created_by: adminEmail,
            updated_by: adminEmail,
          })
          .returningAll()
          .executeTakeFirstOrThrow();

        const sourceUnits = await db
          .selectFrom("member_offer_units" as any)
          .selectAll()
          .where("member_offer_id", "=", id)
          .execute();

        if (sourceUnits.length > 0) {
          await db
            .insertInto("member_offer_units" as any)
            .values(
              sourceUnits.map((u: any) => ({
                member_offer_id: copy.id,
                resort_unit_id: u.resort_unit_id,
                is_admin_override: u.is_admin_override,
                offer_value_type: u.offer_value_type,
                offer_value: u.offer_value,
                minimum_nights: u.minimum_nights,
                maximum_nights_per_booking: u.maximum_nights_per_booking,
                maximum_units_per_membership: u.maximum_units_per_membership,
              })),
            )
            .execute();
        }

        return success(ctx, copy, "Member offer duplicated", 201);
      } catch (err: any) {
        logError("[MemberOffers] DuplicateMemberOffer:", err);
        return error(ctx, err?.message ?? "Failed to duplicate member offer", 500);
      }
    },

    async GetEDMTemplates() {
      try {
        const { EDMService } = await import("@/v1/services/admin/edm/edm.service");
        const result = await EDMService.getInstance().listTemplates();
        if (!result.success) return error(ctx, result.message, 502);
        return success(ctx, result.data);
      } catch (err: any) {
        logError("[MemberOffers] GetEDMTemplates:", err);
        return error(ctx, err?.message ?? "Failed to fetch EDM templates", 500);
      }
    },

    async GetAccountTypes() {
      try {
        if (!memberDb) {
          throw new Error("memberDatastore connection pool is not configured");
        }
        const types = await memberDb
          .selectFrom("member_account_types" as any)
          .select(["id", "name"])
          .where("is_active" as any, "=", true)
          .orderBy("name" as any, "asc")
          .execute()
          .catch(() => []);

        const formatted = types.map((t: any) => ({
          id: String(t.id),
          label: t.name,
        }));
        return success(ctx, formatted);
      } catch (err: any) {
        logError("[MemberOffers] GetAccountTypes:", err);
        return success(ctx, []);
      }
    },

    async GetAccountStatuses() {
      try {
        if (!memberDb) {
          throw new Error("memberDatastore connection pool is not configured");
        }
        const statuses = await memberDb
          .selectFrom("member_account_statuses" as any)
          .select(["id", "name"])
          .where("is_active" as any, "=", true)
          .orderBy("name" as any, "asc")
          .execute()
          .catch(() => []);

        const formatted = statuses.map((s: any) => ({
          id: String(s.id),
          label: s.name,
        }));
        return success(ctx, formatted);
      } catch (err: any) {
        logError("[MemberOffers] GetAccountStatuses:", err);
        return success(ctx, []);
      }
    },

    async GetClubs() {
      try {
        if (!memberDb) {
          throw new Error("memberDatastore connection pool is not configured");
        }
        const clubs = await memberDb
          .selectFrom("membership_clubs" as any)
          .select(["id", "name"])
          .orderBy("name" as any, "asc")
          .execute()
          .catch(() => []);

        const formatted = clubs.map((c: any) => ({
          id: String(c.id),
          label: c.name,
        }));
        return success(ctx, formatted);
      } catch (err: any) {
        logError("[MemberOffers] GetClubs:", err);
        return success(ctx, []);
      }
    },
  };
};
