import { DatabaseError } from "@/lib/error";
import { logError } from "@/lib/logger";
import type { RepositoryContext } from "../datastore/repository";
import { BaseRepository } from "../datastore/repository";
import type { MemberAccessCapabilities } from "./member-access.repository";

export interface MemberAccessRoleInput extends MemberAccessCapabilities {
  name: string;
  description?: string | null;
}

/**
 * Named member-access roles — templates, in the same spirit as
 * `promo_access_roles`. Applying one writes its values onto a user's grant;
 * the role itself is never consulted at request time.
 */
export const MemberAccessRolesRepository = (ctx: RepositoryContext) => {
  const { datastore } = new BaseRepository(ctx);

  const List = async () => {
    try {
      return await datastore
        .selectFrom("member_access_roles")
        .selectAll()
        .orderBy("name", "asc")
        .execute();
    } catch (err) {
      logError(err);
      throw new DatabaseError({
        error: err,
        message: "Failed to list member access roles",
      });
    }
  };

  const Get = async (id: string) => {
    try {
      const record = await datastore
        .selectFrom("member_access_roles")
        .selectAll()
        .where("id", "=", id)
        .executeTakeFirst();
      return record ?? null;
    } catch (err) {
      logError(err);
      throw new DatabaseError({
        error: err,
        message: "Failed to fetch member access role",
      });
    }
  };

  const Create = async (entry: MemberAccessRoleInput, createdBy?: string) => {
    try {
      return await datastore
        .insertInto("member_access_roles")
        .values({ ...entry, created_by: createdBy ?? null })
        .returningAll()
        .executeTakeFirstOrThrow();
    } catch (err) {
      logError(err);
      throw new DatabaseError({
        error: err,
        message: "Failed to create member access role",
      });
    }
  };

  const Update = async (id: string, entry: MemberAccessRoleInput) => {
    try {
      return await datastore
        .updateTable("member_access_roles")
        .set({ ...entry, updated_at: new Date() })
        .where("id", "=", id)
        .returningAll()
        .executeTakeFirstOrThrow();
    } catch (err) {
      logError(err);
      throw new DatabaseError({
        error: err,
        message: "Failed to update member access role",
      });
    }
  };

  const Remove = async (id: string) => {
    try {
      await datastore
        .deleteFrom("member_access_roles")
        .where("id", "=", id)
        .execute();
    } catch (err) {
      logError(err);
      throw new DatabaseError({
        error: err,
        message: "Failed to delete member access role",
      });
    }
  };

  /** Roles assigned to one console user. */
  const ListForUser = async (consoleUserId: string) => {
    try {
      return await datastore
        .selectFrom("console_user_member_access_roles as assignment")
        .innerJoin(
          "member_access_roles as role",
          "role.id",
          "assignment.member_access_role_id",
        )
        .selectAll("role")
        .where("assignment.console_user_id", "=", consoleUserId)
        .orderBy("role.name", "asc")
        .execute();
    } catch (err) {
      logError(err);
      throw new DatabaseError({
        error: err,
        message: "Failed to list assigned member access roles",
      });
    }
  };

  /** Every assignment, for the admin screen. */
  const ListAssignments = async () => {
    try {
      return await datastore
        .selectFrom("console_user_member_access_roles as assignment")
        .innerJoin(
          "member_access_roles as role",
          "role.id",
          "assignment.member_access_role_id",
        )
        .select([
          "assignment.console_user_id",
          "role.id as role_id",
          "role.name as role_name",
        ])
        .orderBy("role.name", "asc")
        .execute();
    } catch (err) {
      logError(err);
      throw new DatabaseError({
        error: err,
        message: "Failed to list member access role assignments",
      });
    }
  };

  const Assign = async (
    consoleUserId: string,
    roleId: string,
    assignedBy?: string,
  ) => {
    try {
      return await datastore
        .insertInto("console_user_member_access_roles")
        .values({
          console_user_id: consoleUserId,
          member_access_role_id: roleId,
          assigned_by: assignedBy ?? null,
        })
        // Assigning twice is a no-op, not an error: the request's intent is
        // "this user should hold this role".
        .onConflict((oc) =>
          oc.columns(["console_user_id", "member_access_role_id"]).doNothing(),
        )
        .executeTakeFirst();
    } catch (err) {
      logError(err);
      throw new DatabaseError({
        error: err,
        message: "Failed to assign member access role",
      });
    }
  };

  const Unassign = async (consoleUserId: string, roleId: string) => {
    try {
      await datastore
        .deleteFrom("console_user_member_access_roles")
        .where("console_user_id", "=", consoleUserId)
        .where("member_access_role_id", "=", roleId)
        .execute();
    } catch (err) {
      logError(err);
      throw new DatabaseError({
        error: err,
        message: "Failed to remove member access role",
      });
    }
  };

  const UnassignAll = async (consoleUserId: string) => {
    try {
      await datastore
        .deleteFrom("console_user_member_access_roles")
        .where("console_user_id", "=", consoleUserId)
        .execute();
    } catch (err) {
      logError(err);
      throw new DatabaseError({
        error: err,
        message: "Failed to clear member access roles",
      });
    }
  };

  return {
    List,
    Get,
    Create,
    Update,
    Remove,
    ListForUser,
    ListAssignments,
    Assign,
    Unassign,
    UnassignAll,
  };
};

export type TMemberAccessRolesRepository = ReturnType<
  typeof MemberAccessRolesRepository
>;
