import { ConsoleActivityLogsRepository } from "@/internal/console-activity-logs/console-activity-logs.repository";
import type { RepositoryContext } from "@/internal/datastore/repository";
import { MemberSessionRepository } from "@/internal/member-session/member-session.repository";
import { MemberSessionServices } from "@/internal/member-session/member-session.services";
import { MemberUserRepository } from "@/internal/members/member-user.repository";
import { MemberRepository } from "@/internal/members/member.repository";
import { MemberServices } from "@/internal/members/member.services";
import { logError } from "@/lib/logger";
import { error as errorResponse, success } from "@/lib/response";
import { forwardToUpdotCore } from "@/lib/updot-forward";
import { resolveMemberAccess } from "@/middlewares/withMemberAccess";
import type { Context } from "hono";

/** `a,b,c` query params → string[]; empty means "no filter". */
const csvParam = (value?: string) =>
  value
    ? value
        .split(",")
        .map((part) => part.trim())
        .filter(Boolean)
    : undefined;

const memberServicesFor = (c: Context) =>
  MemberServices({
    MemberRepository: MemberRepository(c as RepositoryContext),
    MemberUserRepository: MemberUserRepository(c as RepositoryContext),
    MemberSessionRepository: MemberSessionRepository(c as RepositoryContext),
  });

export const listMembershipsHandler = async (c: Context) => {
  try {
    const query = c.req.query();
    const access = await resolveMemberAccess(c);
    const result = await memberServicesFor(c).GetPaginatedMemberShips({
      page: Number.parseInt(query.page ?? "1", 10) || 1,
      // Clamped: an unbounded pageSize turns one request into a full-table read
      // over the member DB, which is what the pagination exists to avoid.
      limit: Math.min(
        Math.max(Number.parseInt(query.pageSize ?? "50", 10) || 50, 1),
        200,
      ),
      searchTerm: query.searchTerm || undefined,
      accountTypeIds: csvParam(query.accountTypeIds),
      accountStatusIds: csvParam(query.accountStatusIds),
      isActive:
        query.isActive === "true"
          ? true
          : query.isActive === "false"
            ? false
            : undefined,
      createdFrom: query.createdFrom || undefined,
      createdTo: query.createdTo || undefined,
      scopeTypeIds: access.account_type_ids,
      scopeStatusIds: access.account_status_ids,
    });

    return success(c, result, "Memberships retrieved successfully");
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to list memberships", 500, err);
  }
};

export const getAccountOverviewHandler = async (c: Context) => {
  try {
    const membershipNumber = c.req.param("membershipNumber");
    if (!membershipNumber) {
      return errorResponse(c, "Membership number is required", 400);
    }
    const services = memberServicesFor(c);
    const access = await resolveMemberAccess(c);
    if (
      !(await services.IsMembershipInScope(
        { membershipNumber },
        {
          accountTypeIds: access.account_type_ids,
          accountStatusIds: access.account_status_ids,
        },
      ))
    ) {
      return errorResponse(c, "This account is outside your access", 403);
    }
    const overview = await services.GetAccountOverview(membershipNumber);
    return success(c, overview, "Account overview retrieved successfully");
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to fetch account overview", 500, err);
  }
};

// On demand only: hitting Viewpoint on every member page load was slow and
// pointless when nobody was looking at points.
export const getMemberPointsHandler = async (c: Context) => {
  try {
    const membershipNumber = c.req.param("membershipNumber");
    if (!membershipNumber) {
      return errorResponse(c, "Membership number is required", 400);
    }
    const points =
      await memberServicesFor(c).GetMemberPointsBalance(membershipNumber);
    return success(c, points, "Points retrieved successfully");
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to fetch member points", 502, err);
  }
};

export const getMembershipAnalyticsHandler = async (c: Context) => {
  try {
    const query = c.req.query();
    // Country groups are capped so one huge cut can't dominate the response.
    const requested = Number.parseInt(query.countryLimit ?? "", 10);
    const countryLimit = Number.isFinite(requested)
      ? Math.min(Math.max(requested, 50), 1000)
      : 200;

    // Same filters as the table, plus the caller's access slice: the charts must
    // describe the rows the caller can see, or a scoped user reads global
    // figures and a filtered table sits under an unfiltered chart.
    const access = await resolveMemberAccess(c);
    const analytics = await memberServicesFor(c).GetMembershipAnalytics(
      {
        searchTerm: query.searchTerm || undefined,
        accountTypeIds: csvParam(query.accountTypeIds),
        accountStatusIds: csvParam(query.accountStatusIds),
        isActive:
          query.isActive === "true"
            ? true
            : query.isActive === "false"
              ? false
              : undefined,
        createdFrom: query.createdFrom || undefined,
        createdTo: query.createdTo || undefined,
        scopeTypeIds: access.account_type_ids,
        scopeStatusIds: access.account_status_ids,
      },
      countryLimit,
    );
    return success(c, analytics, "Analytics retrieved successfully");
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to fetch member analytics", 500, err);
  }
};

export const getMembershipFilterOptionsHandler = async (c: Context) => {
  try {
    const options = await memberServicesFor(c).GetMembershipFilterOptions();
    return success(c, options, "Filter options retrieved successfully");
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to fetch filter options", 500, err);
  }
};

export const getAccountByMembershipNumberHandler = async (c: Context) => {
  try {
    const membershipNumber = c.req.param("membershipNumber");
    if (!membershipNumber) {
      return errorResponse(c, "Membership number is required", 400);
    }
    const services = memberServicesFor(c);
    const access = await resolveMemberAccess(c);
    if (
      !(await services.IsMembershipInScope(
        { membershipNumber },
        {
          accountTypeIds: access.account_type_ids,
          accountStatusIds: access.account_status_ids,
        },
      ))
    ) {
      return errorResponse(c, "This account is outside your access", 403);
    }
    const account =
      await services.GetAccountByMembershipNumber(membershipNumber);
    if (!account) {
      return errorResponse(c, "Wrong Membership Number", 404);
    }
    return success(c, { account }, "Account retrieved successfully");
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to fetch account", 500, err);
  }
};

export const getContactsByMembershipNumberHandler = async (c: Context) => {
  try {
    const membershipNumber = c.req.param("membershipNumber");
    if (!membershipNumber) {
      return errorResponse(c, "Membership number is required", 400);
    }
    const services = memberServicesFor(c);
    const access = await resolveMemberAccess(c);
    if (
      !(await services.IsMembershipInScope(
        { membershipNumber },
        {
          accountTypeIds: access.account_type_ids,
          accountStatusIds: access.account_status_ids,
        },
      ))
    ) {
      return errorResponse(c, "This account is outside your access", 403);
    }
    const members =
      await services.GetMembersByMembershipNumber(membershipNumber);
    return success(c, { members }, "Contacts retrieved successfully");
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to fetch membership contacts", 500, err);
  }
};

export const getMemberByMemberNumberHandler = async (c: Context) => {
  try {
    const memberNumber = c.req.param("memberNumber");
    if (!memberNumber) {
      return errorResponse(c, "Member number is required", 400);
    }
    const services = memberServicesFor(c);
    const access = await resolveMemberAccess(c);
    if (
      !(await services.IsMembershipInScope(
        { memberNumber },
        {
          accountTypeIds: access.account_type_ids,
          accountStatusIds: access.account_status_ids,
        },
      ))
    ) {
      return errorResponse(c, "This member is outside your access", 403);
    }
    const member = await services.GetMemberByMemberNumber(memberNumber);
    if (!member) {
      return errorResponse(c, "Wrong Member Number", 404);
    }
    return success(c, { member }, "Member retrieved successfully");
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to fetch member", 500, err);
  }
};

export const getMemberSessionsHandler = async (c: Context) => {
  try {
    const userId = c.req.param("userId");
    if (!userId) {
      return errorResponse(c, "User id is required", 400);
    }
    const memberSessionServices = MemberSessionServices({
      MemberSessionRepository: MemberSessionRepository(c as RepositoryContext),
      CTX: c,
    });
    const sessions =
      await memberSessionServices.GetMemberSessionsByUserId(userId);
    return success(
      c,
      { sessions: sessions ?? [] },
      "Sessions retrieved successfully",
    );
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to fetch member sessions", 500, err);
  }
};

// Sessions by member record id — the console has the member id on hand, and
// this saves it from having to carry user_id around.
export const getMemberSessionLogsHandler = async (c: Context) => {
  try {
    const memberRecordId = c.req.param("memberRecordId");
    if (!memberRecordId) {
      return errorResponse(c, "Member record id is required", 400);
    }
    const sessions =
      await memberServicesFor(c).GetMemberSessions(memberRecordId);
    if (sessions === null) {
      return errorResponse(c, "Member has no linked user", 404);
    }
    return success(c, { sessions }, "Sessions retrieved successfully");
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to fetch member sessions", 500, err);
  }
};

export const getMemberSavedSearchesHandler = async (c: Context) => {
  try {
    const memberRecordId = c.req.param("memberRecordId");
    if (!memberRecordId) {
      return errorResponse(c, "Member record id is required", 400);
    }
    const savedSearches =
      await memberServicesFor(c).GetMemberSavedSearches(memberRecordId);
    return success(
      c,
      { saved_searches: savedSearches },
      "Saved searches retrieved successfully",
    );
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to fetch member saved searches", 500, err);
  }
};

// Activity on one member, read from Core's `console_activity_logs` audit table
// in the member DB. Behind `members:read` so the member page works without the
// separate `admin-logs` role.
export const getMemberActivityLogsHandler = async (c: Context) => {
  try {
    const memberRecordId = c.req.param("memberRecordId");
    if (!memberRecordId) {
      return errorResponse(c, "Member record id is required", 400);
    }
    const userId = c.req.query("userId") || undefined;
    const actorType = c.req.query("actorType");

    const repo = ConsoleActivityLogsRepository(c as RepositoryContext);
    const logs = await repo.FindEntriesByEntityIDs({
      entityIDs: userId ? [memberRecordId, userId] : [memberRecordId],
      featureSlugs: ["members"],
      actorType:
        actorType === "member" || actorType === "console_user"
          ? actorType
          : undefined,
    });
    return success(c, { logs }, "Member logs retrieved successfully");
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to fetch member logs", 500, err);
  }
};

export const updateMemberPasswordHandler = async (c: Context) => {
  try {
    const memberRecordId = c.req.param("memberRecordId");
    if (!memberRecordId) {
      return errorResponse(c, "Member record id is required", 400);
    }
    const body = (await c.req.json()) as { newPassword: string };
    const updated = await memberServicesFor(c).UpdateMemberPassword(
      memberRecordId,
      body.newPassword,
    );
    if (!updated) {
      return errorResponse(c, "User not found", 404);
    }
    c.set("entityId", memberRecordId);
    return success(c, null, "Password updated successfully");
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to update member password", 500, err);
  }
};

// Viewpoint profile sync stays Core's job (it drives the Viewpoint worker), but
// the guard lives here so only super admins can trigger it.
export const syncMemberProfileHandler = async (c: Context) => {
  try {
    const profileID = c.req.param("profileID");
    if (!profileID) {
      return errorResponse(c, "Profile id is required", 400);
    }
    const body = await c.req.text();
    return await forwardToUpdotCore(
      `/v1/admin/members/sync-profile/${profileID}`,
      { method: "PUT", body },
    );
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to sync member profile", 500, err);
  }
};

// Booking history for a membership — internal (destination), external (RCI) and
// curated events all live in Core.
export const getMembershipBookingsHandler = async (c: Context) => {
  try {
    const membershipNumber = c.req.param("membershipNumber");
    if (!membershipNumber) {
      return errorResponse(c, "Membership number is required", 400);
    }
    const query = new URLSearchParams(c.req.query()).toString();
    return await forwardToUpdotCore(
      `/v1/admin/memberships/${membershipNumber}/bookings${query ? `?${query}` : ""}`,
    );
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to fetch membership bookings", 500, err);
  }
};

export const getCuratedBookingsHandler = async (c: Context) => {
  try {
    const query = new URLSearchParams(c.req.query()).toString();
    return await forwardToUpdotCore(
      `/v1/admin/curated-events/bookings/list${query ? `?${query}` : ""}`,
    );
  } catch (err) {
    logError(err);
    return errorResponse(c, "Failed to fetch curated bookings", 500, err);
  }
};
