import { TConsoleRolePrivilegesServices } from "@/internal/console-role-privileges/console-role-privileges.services";
import { TConsoleUserRolesServices } from "@/internal/console-user-roles/console-user-roles.services";
import { TConsoleUserSessionServices } from "@/internal/console-user-sessions/console-user-session.services";
import { TConsoleUserServices } from "@/internal/console-users/console-users.services";
import {
  CONSOLE_USER_ROLES_TOKEN_DEFAULTS,
  CONSOLE_USER_SESSION_ID_TOKEN_DEFAULTS,
  CONSOLE_USER_SESSION_TOKEN_DEFAULTS,
  getBaseCookieConfig,
} from "@/lib/cookie";
import { logError } from "@/lib/logger";
import { error, success } from "@/lib/response";
import { Context } from "hono";
import { getCookie, setCookie } from "hono/cookie";
import z from "zod";
import {
  RegisterConsoleUserSchema,
  RequestPasswordResetSchema,
  RotateConsoleUserSessionSchema,
  SearchConsoleUserSchema,
  SetPasswordSchema,
  SigninConsoleUserSchema,
  SsoSigninConsoleUserSchema,
  UpdateConsoleUserSchema,
  UpdateSuperAdminSchema,
} from "./console-users.schema";

type TConsoleUserControllerDeps = {
  ConsoleUserServices: TConsoleUserServices;
  ConsoleUserSessionServices: TConsoleUserSessionServices;
  ConsoleUserRolesServices: TConsoleUserRolesServices;
  ConsoleRolePrivilegesServices: TConsoleRolePrivilegesServices;
};

export const ConsoleUserController = (
  ctx: Context,
  {
    ConsoleUserServices,
    ConsoleUserSessionServices,
    ConsoleUserRolesServices,
    ConsoleRolePrivilegesServices,
  }: TConsoleUserControllerDeps,
) => {
  const CheckSuperAdminExists = async () => {
    try {
      const superAdmins = await ConsoleUserServices.GetSuperAdmins();
      return success(ctx, { exists: superAdmins.length > 0 });
    } catch (err) {
      logError(err);
      return error(ctx, undefined, undefined, err);
    }
  };

  const RegisterInitialSuperAdmin = async () => {
    try {
      const superAdmins = await ConsoleUserServices.GetSuperAdmins();
      if (superAdmins.length > 0) {
        return error(ctx, "Super admin already exists", 403);
      }
      const raw: z.infer<typeof RegisterConsoleUserSchema> =
        await ctx.req.json();
      const newUser = await ConsoleUserServices.RegisterNewUser({
        ...raw,
        isSuperAdmin: true,
      });
      return success(ctx, newUser);
    } catch (err) {
      logError(err);
      return error(ctx, undefined, undefined, err);
    }
  };

  const RegisterConsoleUser = async () => {
    try {
      const raw: z.infer<typeof RegisterConsoleUserSchema> =
        await ctx.req.json();
      const newUser = await ConsoleUserServices.RegisterNewUser(raw);
      return success(ctx, newUser);
    } catch (err) {
      logError(err);
      return error(ctx, undefined, undefined, err);
    }
  };
  const SigninConsoleUser = async () => {
    try {
      const raw: z.infer<typeof SigninConsoleUserSchema> = await ctx.req.json();
      if (!raw.email && !raw?.password) {
        return error(ctx, "Provide Email or User Name", 400);
      }
      const newSession = await ConsoleUserServices.SigninConsoleUser({
        browser: raw?.browser ?? "",
        deviceType: raw?.deviceType ?? "",
        fcmToken: raw?.fcmToken ?? "",
        ipAddress: raw?.ipAddress ?? "",
        location: raw?.location ?? "",
        os: raw?.os,
        password: raw?.password,
        email: raw?.email,
        username: raw?.username,
      });
      if (!newSession || !newSession.encryptedSessionToken || !newSession?.id) {
        return error(ctx, "Failed to signin. Please try again");
      }

      await IssueSessionCookies({
        id: newSession.id,
        console_user_id: newSession.console_user_id,
        encryptedSessionToken: newSession.encryptedSessionToken,
      });
      return success(ctx, {});
    } catch (err) {
      logError(err);
      return error(ctx, undefined, undefined, err);
    }
  };

  /**
   * Writes the session, session-id and role cookies for a freshly created session.
   *
   * Shared by password sign-in and one-time-link sign-in so both land the caller in
   * exactly the same authenticated state — in particular the role token, which the
   * console reads client-side to decide what it renders.
   */
  const IssueSessionCookies = async (newSession: {
    id: string;
    console_user_id?: string | null;
    encryptedSessionToken: string;
  }) => {
    // Find all roles of a user and create a token out of it and save it in cookies
    const roleTokenRes =
      await ConsoleRolePrivilegesServices.CreateRolePrivilegesToken(
        newSession.console_user_id!,
      );
    const roleToken = roleTokenRes?.roleToken;
    if (roleToken) {
      setCookie(ctx, CONSOLE_USER_ROLES_TOKEN_DEFAULTS.name, roleToken, {
        ...getBaseCookieConfig(),
        httpOnly: false,
        maxAge: CONSOLE_USER_ROLES_TOKEN_DEFAULTS.maxAge,
      });
    }

    setCookie(
      ctx,
      CONSOLE_USER_SESSION_TOKEN_DEFAULTS.name,
      newSession.encryptedSessionToken,
      {
        ...getBaseCookieConfig(),
        maxAge: CONSOLE_USER_SESSION_TOKEN_DEFAULTS.maxAge,
      },
    );
    setCookie(
      ctx,
      CONSOLE_USER_SESSION_ID_TOKEN_DEFAULTS.name,
      newSession?.id,
      {
        ...getBaseCookieConfig(),
        maxAge: CONSOLE_USER_SESSION_ID_TOKEN_DEFAULTS.maxAge,
      },
    );
  };

  /**
   * Redeems a one-time sign-in link from an email.
   *
   * Kept deliberately terse in what it returns: on failure the reason is a generic
   * message, because the caller is an unauthenticated request holding a token and
   * has no business learning whose account it belonged to or why it was refused.
   */
  const SsoSigninConsoleUser = async () => {
    try {
      const raw: z.infer<typeof SsoSigninConsoleUserSchema> =
        await ctx.req.json();

      const newSession = await ConsoleUserServices.SsoSigninConsoleUser({
        token: raw.token,
        browser: raw?.browser,
        deviceType: raw?.deviceType,
        fcmToken: raw?.fcmToken,
        ipAddress: raw?.ipAddress,
        location: raw?.location,
        os: raw?.os,
      });
      if (!newSession || !newSession.encryptedSessionToken || !newSession?.id) {
        return error(ctx, "This sign-in link is no longer valid.", 401);
      }

      await IssueSessionCookies({
        id: newSession.id,
        console_user_id: newSession.console_user_id,
        encryptedSessionToken: newSession.encryptedSessionToken,
      });
      return success(ctx, {});
    } catch (err) {
      logError(err);
      return error(
        ctx,
        (err as Error)?.message ?? "This sign-in link is no longer valid.",
        401,
      );
    }
  };

  const RotateConsoleUserSession = async () => {
    try {
      const raw: z.infer<typeof RotateConsoleUserSessionSchema> =
        await ctx.req.json();
      const admin_session_id = getCookie(
        ctx,
        CONSOLE_USER_SESSION_ID_TOKEN_DEFAULTS.name,
      );
      if (!admin_session_id) {
        setCookie(ctx, CONSOLE_USER_SESSION_ID_TOKEN_DEFAULTS.name, "", {
          ...getBaseCookieConfig(),
          maxAge: 0,
        });

        setCookie(ctx, CONSOLE_USER_SESSION_TOKEN_DEFAULTS.name, "", {
          ...getBaseCookieConfig(),
          maxAge: 0,
        });

        setCookie(ctx, CONSOLE_USER_ROLES_TOKEN_DEFAULTS.name, "", {
          ...getBaseCookieConfig(),
          maxAge: 0,
        });
        return error(ctx, "Invalid session.", 403);
      }
      const newSession = await ConsoleUserSessionServices.RotateSession({
        fcm_token: raw.fcmToken,
        browser: raw.browser,
        device_type: raw.deviceType,
        ip_address: raw.ipAddress,
        location: raw.location,
        os: raw.os,
        kcSessionId: admin_session_id,
      });
      if (
        !newSession ||
        !newSession?.encryptedSessionToken ||
        !newSession?.id
      ) {
        setCookie(ctx, CONSOLE_USER_SESSION_ID_TOKEN_DEFAULTS.name, "", {
          ...getBaseCookieConfig(),
          maxAge: 0,
        });

        setCookie(ctx, CONSOLE_USER_SESSION_TOKEN_DEFAULTS.name, "", {
          ...getBaseCookieConfig(),
          maxAge: 0,
        });

        setCookie(ctx, CONSOLE_USER_ROLES_TOKEN_DEFAULTS.name, "", {
          ...getBaseCookieConfig(),
          maxAge: 0,
        });
        return error(ctx, "Failed Rotating Session", 403);
      }

      // Find all roles of a user and create a token out of it and save it in cookies
      const roleTokenRes =
        await ConsoleRolePrivilegesServices.CreateRolePrivilegesToken(
          newSession.console_user_id!,
        );
      const roleToken = roleTokenRes?.roleToken;
      if (roleToken) {
        setCookie(ctx, CONSOLE_USER_ROLES_TOKEN_DEFAULTS.name, roleToken, {
          ...getBaseCookieConfig(),
          httpOnly: false,
          maxAge: CONSOLE_USER_ROLES_TOKEN_DEFAULTS.maxAge,
        });
      }

      setCookie(
        ctx,
        CONSOLE_USER_SESSION_TOKEN_DEFAULTS.name,
        newSession.encryptedSessionToken,
        {
          ...getBaseCookieConfig(),
          maxAge: CONSOLE_USER_SESSION_TOKEN_DEFAULTS.maxAge,
        },
      );
      setCookie(
        ctx,
        CONSOLE_USER_SESSION_ID_TOKEN_DEFAULTS.name,
        newSession?.id,
        {
          ...getBaseCookieConfig(),
          maxAge: CONSOLE_USER_SESSION_ID_TOKEN_DEFAULTS.maxAge,
        },
      );
      return success(ctx, {}, "Session refreshed");
    } catch (err) {
      logError(err);
      return error(ctx, undefined, undefined, err);
    }
  };

  const SignoutHandler = async () => {
    try {
      const admin_session_id = getCookie(
        ctx,
        CONSOLE_USER_SESSION_ID_TOKEN_DEFAULTS.name,
      );
      if (admin_session_id) {
        await ConsoleUserSessionServices.DeleteSession(admin_session_id);
      }

      setCookie(ctx, CONSOLE_USER_SESSION_ID_TOKEN_DEFAULTS.name, "", {
        ...getBaseCookieConfig(),
        maxAge: 0,
      });

      setCookie(ctx, CONSOLE_USER_SESSION_TOKEN_DEFAULTS.name, "", {
        ...getBaseCookieConfig(),
        maxAge: 0,
      });

      setCookie(ctx, CONSOLE_USER_ROLES_TOKEN_DEFAULTS.name, "", {
        ...getBaseCookieConfig(),
        maxAge: 0,
      });
      return success(ctx, {}, "Session revoked");
    } catch (err) {
      logError(err);
      return error(ctx, undefined, undefined, err);
    }
  };

  const CreateSuperAdmin = async () => {
    try {
      const raw: z.infer<typeof RegisterConsoleUserSchema> =
        await ctx.req.json();
      const { users } = await ConsoleUserServices.FindAllConsoleUsers();
      if (users?.length > 0) {
        return error(ctx, "Super Admin is already registered");
      }
      const newUser = await ConsoleUserServices.RegisterNewUser(raw);
      return success(ctx, newUser);
    } catch (err) {
      logError(err);
      return error(ctx, undefined, undefined, err);
    }
  };

  const SearchConsoleUser = async () => {
    try {
      const raw: z.infer<typeof SearchConsoleUserSchema> =
        await ctx.req.query();
      const consoleUsers = await ConsoleUserServices.FindAllConsoleUsers({
        page: parseInt(raw.page ?? "1"),
        pageSize: parseInt(raw?.pageSize ?? "50"),
        searchTerm: raw?.searchTerm,
        isActive:
          raw?.isActive === "true"
            ? true
            : raw?.isActive === "false"
              ? false
              : undefined,
        isSuperAdmin:
          raw?.isSuperAdmin === "true"
            ? true
            : raw?.isSuperAdmin === "false"
              ? false
              : undefined,
        roleIds: raw?.roleIds
          ? raw.roleIds
              .split(",")
              .map((id) => id.trim())
              .filter(Boolean)
          : undefined,
      });
      return success(ctx, consoleUsers);
    } catch (err) {
      logError(err);
      return error(ctx, undefined, undefined, err);
    }
  };

  const UpdateConsoleUser = async () => {
    try {
      const raw: z.infer<typeof UpdateConsoleUserSchema> = await ctx.req.json();
      const { id } = ctx.req.param();
      const updated = await ConsoleUserServices.UpdateConsoleUser(id, {
        firstName: raw.firstName,
        lastName: raw.lastName,
        password: raw.password,
        isActive: raw.isActive,
        isDeleted: raw.isDeleted,
        isSuperAdmin: raw.isSuperAdmin,
        roles: raw.roles,
      });
      return success(ctx, updated);
    } catch (err) {
      logError(err);
      return error(ctx, undefined, undefined, err);
    }
  };

  const FindConsoleUser = async () => {
    try {
      const { id } = ctx.req.param();
      const userRecord = await ConsoleUserServices.GetUserById(id);
      return success(ctx, userRecord);
    } catch (err) {
      logError(err);
      return error(ctx, undefined, undefined, err);
    }
  };

  const UpdateSuperAdmin = async () => {
    try {
      const raw: z.infer<typeof UpdateSuperAdminSchema> = await ctx.req.json();
      const existingSuperAdmins = await ConsoleUserServices.GetSuperAdmins();
      if (existingSuperAdmins?.length > 0) {
        return error(ctx, "Super Admin is already registered");
      }
      const existingUser = await ConsoleUserServices.GetByEmail(raw.email);
      if (!existingUser) {
        return error(ctx, "Email not registered in our system");
      }
      const updated = await ConsoleUserServices.UpdateConsoleUser(
        existingUser?.id!,
        {
          isSuperAdmin: true,
        },
      );
      return success(ctx, updated);
    } catch (err) {
      logError(err);
      return error(ctx, undefined, undefined, err);
    }
  };

  const Me = async () => {
    try {
      const admin_session_id = getCookie(
        ctx,
        CONSOLE_USER_SESSION_ID_TOKEN_DEFAULTS.name,
      );
      if (!admin_session_id) {
        return error(ctx, "Unauthorized", 401);
      }
      const session =
        await ConsoleUserSessionServices.GetUserSession(admin_session_id);
      if (!session || !session.consoleUserRecord) {
        return error(ctx, "Unauthorized", 401);
      }
      const user = await ConsoleUserServices.Me(session.consoleUserRecord.id);
      return success(ctx, { user, valid: true });
    } catch (err) {
      logError(err);
      return error(ctx, undefined, undefined, err);
    }
  };

  const SetPassword = async () => {
    try {
      const raw: z.infer<typeof SetPasswordSchema> = await ctx.req.json();
      const result = await ConsoleUserServices.SetPasswordByToken(raw);
      return success(ctx, result);
    } catch (err) {
      logError(err);
      return error(ctx, undefined, undefined, err);
    }
  };

  const RequestPasswordReset = async () => {
    try {
      const raw: z.infer<typeof RequestPasswordResetSchema> =
        await ctx.req.json();
      const result = await ConsoleUserServices.RequestPasswordReset(raw.email);
      return success(ctx, result);
    } catch (err) {
      logError(err);
      return error(ctx, undefined, undefined, err);
    }
  };

  return {
    RegisterConsoleUser,
    SigninConsoleUser,
    SsoSigninConsoleUser,
    RotateConsoleUserSession,
    SignoutHandler,
    CreateSuperAdmin,
    SearchConsoleUser,
    UpdateConsoleUser,
    FindConsoleUser,
    UpdateSuperAdmin,
    Me,
    CheckSuperAdminExists,
    RegisterInitialSuperAdmin,
    SetPassword,
    RequestPasswordReset,
  };
};
