/**
 * Client-Side Authentication Store
 * Maintains session state using secure httpOnly cookies issued by the BFF.
 */

import { create } from "zustand";
import { apiClient } from "@/lib/api";
import { getNullableData } from "@/lib/api/response-helpers";
import { getErrorInfo } from "@/lib/utils/error-handling";
import { logger } from "@/lib/logger";
import {
  authResponseSchema,
  checkPasswordNeededResponseSchema,
  linkWhmcsResponseSchema,
  type AuthTokens,
  type CheckPasswordNeededResponse,
  type LinkWhmcsRequest,
  type LinkWhmcsResponse,
  type LoginRequest,
  type SignupRequest,
} from "@customer-portal/domain/auth";
import type { AuthenticatedUser } from "@customer-portal/domain/customer";
import {
  clearLogoutReason,
  logoutReasonFromErrorCode,
  setLogoutReason,
  type LogoutReason,
} from "@/features/auth/utils/logout-reason";

interface SessionState {
  accessExpiresAt?: string;
  refreshExpiresAt?: string;
}

export interface AuthState {
  user: AuthenticatedUser | null;
  session: SessionState;
  isAuthenticated: boolean;
  loading: boolean;
  error: string | null;
  hasCheckedAuth: boolean;

  login: (credentials: LoginRequest) => Promise<void>;
  signup: (data: SignupRequest) => Promise<void>;
  logout: (options?: { reason?: LogoutReason }) => Promise<void>;
  requestPasswordReset: (email: string) => Promise<void>;
  resetPassword: (token: string, password: string) => Promise<void>;
  changePassword: (currentPassword: string, newPassword: string) => Promise<void>;
  checkPasswordNeeded: (email: string) => Promise<CheckPasswordNeededResponse>;
  linkWhmcs: (request: LinkWhmcsRequest) => Promise<LinkWhmcsResponse>;
  setPassword: (email: string, password: string) => Promise<void>;
  refreshUser: () => Promise<void>;
  refreshSession: () => Promise<void>;
  checkAuth: () => Promise<void>;
  clearError: () => void;
  clearLoading: () => void;
  hydrateUserProfile: (profile: Partial<AuthenticatedUser>) => void;
}

type AuthResponseData = {
  user: AuthenticatedUser;
  tokens: AuthTokens;
};

export const useAuthStore = create<AuthState>()((set, get) => {
  const applyAuthResponse = (data: AuthResponseData, keepLoading = false) => {
    set({
      user: data.user,
      session: {
        accessExpiresAt: data.tokens.expiresAt,
        refreshExpiresAt: data.tokens.refreshExpiresAt,
      },
      isAuthenticated: true,
      loading: keepLoading,
      error: null,
    });
  };

  let refreshPromise: Promise<void> | null = null;

  const runTokenRefresh = async (): Promise<void> => {
    try {
      const response = await apiClient.POST("/api/auth/refresh", { body: {} });
      const parsed = authResponseSchema.safeParse(response.data);
      if (!parsed.success) {
        throw new Error(parsed.error.issues?.[0]?.message ?? "Session refresh failed");
      }
      applyAuthResponse(parsed.data);
    } catch (error) {
      logger.error("Failed to refresh session", error);
      const errorInfo = getErrorInfo(error);
      const reason = logoutReasonFromErrorCode(errorInfo.code) ?? ("session-expired" as const);
      await get().logout({ reason });
      throw error;
    }
  };

  const ensureSingleRefresh = async (): Promise<void> => {
    if (!refreshPromise) {
      refreshPromise = (async () => {
        try {
          await runTokenRefresh();
        } finally {
          refreshPromise = null;
        }
      })();
    }

    await refreshPromise;
  };

  // Set up global listener for 401 errors from API client
  if (typeof window !== "undefined") {
    type AuthUnauthorizedDetail = { url?: string; status?: number };
    window.addEventListener("auth:unauthorized", event => {
      const customEvent = event as CustomEvent<AuthUnauthorizedDetail>;
      const detail = customEvent.detail;
      logger.warn("401 Unauthorized detected - triggering logout", {
        url: detail?.url,
        status: detail?.status,
      });
      void get().logout({ reason: "session-expired" });
    });
  }

  return {
    user: null,
    session: {},
    isAuthenticated: false,
    loading: false,
    error: null,
    hasCheckedAuth: false,

    login: async credentials => {
      set({ loading: true, error: null });
      try {
        const response = await apiClient.POST("/api/auth/login", {
          body: credentials,
          disableCsrf: true, // Public auth endpoint, exempt from CSRF
        });
        const parsed = authResponseSchema.safeParse(response.data);
        if (!parsed.success) {
          throw new Error(parsed.error.issues?.[0]?.message ?? "Login failed");
        }
        applyAuthResponse(parsed.data, true); // Keep loading for redirect
      } catch (error) {
        const errorInfo = getErrorInfo(error);
        set({ loading: false, error: errorInfo.message, isAuthenticated: false });
        throw error;
      }
    },

    signup: async data => {
      set({ loading: true, error: null });
      try {
        const response = await apiClient.POST("/api/auth/signup", {
          body: data,
          disableCsrf: true, // Public auth endpoint, exempt from CSRF
        });
        const parsed = authResponseSchema.safeParse(response.data);
        if (!parsed.success) {
          throw new Error(parsed.error.issues?.[0]?.message ?? "Signup failed");
        }
        applyAuthResponse(parsed.data);
      } catch (error) {
        set({
          loading: false,
          error: error instanceof Error ? error.message : "Signup failed",
        });
        throw error;
      }
    },

    logout: async (options?: { reason?: LogoutReason }) => {
      if (options?.reason) {
        setLogoutReason(options.reason);
      } else {
        clearLogoutReason();
      }
      try {
        await apiClient.POST("/api/auth/logout", {});
      } catch (error) {
        logger.warn("Logout API call failed", { error });
      } finally {
        set({
          user: null,
          session: {},
          isAuthenticated: false,
          error: null,
          loading: false,
        });
      }
    },

    requestPasswordReset: async (email: string) => {
      set({ loading: true, error: null });
      try {
        await apiClient.POST("/api/auth/request-password-reset", {
          body: { email },
          disableCsrf: true, // Public auth endpoint, exempt from CSRF
        });
        set({ loading: false });
      } catch (error) {
        set({
          loading: false,
          error: error instanceof Error ? error.message : "Password reset request failed",
        });
        throw error;
      }
    },

    resetPassword: async (token: string, password: string) => {
      set({ loading: true, error: null });
      try {
        const response = await apiClient.POST("/api/auth/reset-password", {
          body: { token, password },
          disableCsrf: true, // Public auth endpoint, exempt from CSRF
        });
        const parsed = authResponseSchema.safeParse(response.data);
        if (!parsed.success) {
          throw new Error(parsed.error.issues?.[0]?.message ?? "Password reset failed");
        }
        applyAuthResponse(parsed.data);
      } catch (error) {
        set({
          loading: false,
          error: error instanceof Error ? error.message : "Password reset failed",
        });
        throw error;
      }
    },

    changePassword: async (currentPassword: string, newPassword: string) => {
      set({ loading: true, error: null });
      try {
        const response = await apiClient.POST("/api/auth/change-password", {
          body: { currentPassword, newPassword },
        });
        const parsed = authResponseSchema.safeParse(response.data);
        if (!parsed.success) {
          throw new Error(parsed.error.issues?.[0]?.message ?? "Password change failed");
        }
        applyAuthResponse(parsed.data);
      } catch (error) {
        set({
          loading: false,
          error: error instanceof Error ? error.message : "Password change failed",
        });
        throw error;
      }
    },

    checkPasswordNeeded: async (email: string) => {
      set({ loading: true, error: null });
      try {
        const response = await apiClient.POST("/api/auth/check-password-needed", {
          body: { email },
          disableCsrf: true, // Public auth endpoint, exempt from CSRF
        });

        const parsed = checkPasswordNeededResponseSchema.safeParse(response.data);
        if (!parsed.success) {
          throw new Error(parsed.error.issues?.[0]?.message ?? "Check failed");
        }

        set({ loading: false });
        return parsed.data;
      } catch (error) {
        set({
          loading: false,
          error: error instanceof Error ? error.message : "Check failed",
        });
        throw error;
      }
    },

    linkWhmcs: async (linkRequest: LinkWhmcsRequest) => {
      set({ loading: true, error: null });
      try {
        const response = await apiClient.POST("/api/auth/link-whmcs", {
          body: linkRequest,
          disableCsrf: true, // Public auth endpoint, exempt from CSRF
        });

        const parsed = linkWhmcsResponseSchema.safeParse(response.data);
        if (!parsed.success) {
          throw new Error(parsed.error.issues?.[0]?.message ?? "WHMCS link failed");
        }

        set({ loading: false });
        return parsed.data;
      } catch (error) {
        set({
          loading: false,
          error: error instanceof Error ? error.message : "WHMCS link failed",
        });
        throw error;
      }
    },

    setPassword: async (email: string, password: string) => {
      set({ loading: true, error: null });
      try {
        const response = await apiClient.POST("/api/auth/set-password", {
          body: { email, password },
          disableCsrf: true, // Public auth endpoint, exempt from CSRF
        });
        const parsed = authResponseSchema.safeParse(response.data);
        if (!parsed.success) {
          throw new Error(parsed.error.issues?.[0]?.message ?? "Set password failed");
        }
        applyAuthResponse(parsed.data);
      } catch (error) {
        set({
          loading: false,
          error: error instanceof Error ? error.message : "Set password failed",
        });
        throw error;
      }
    },

    refreshUser: async () => {
      const fetchProfile = async (): Promise<void> => {
        const response = await apiClient.GET<{
          isAuthenticated?: boolean;
          user?: AuthenticatedUser;
        }>("/api/auth/me");
        const data = getNullableData(response);
        if (data?.isAuthenticated && data.user) {
          set({
            user: data.user,
            isAuthenticated: true,
            error: null,
          });
        } else {
          set({ user: null, isAuthenticated: false, session: {} });
        }
      };

      try {
        await fetchProfile();
      } catch (error) {
        const errorInfo = getErrorInfo(error);
        if (errorInfo.shouldLogout) {
          const reason = logoutReasonFromErrorCode(errorInfo.code) ?? ("session-expired" as const);
          await get().logout({ reason });
          return;
        }

        try {
          await ensureSingleRefresh();
          await fetchProfile();
        } catch (refreshError) {
          logger.error("Failed to refresh session after auth error", refreshError);
          return;
        }
      }
    },

    refreshSession: async () => {
      await ensureSingleRefresh();
    },

    checkAuth: async () => {
      set({ loading: true });
      try {
        await get().refreshUser();
      } finally {
        set({ hasCheckedAuth: true, loading: false });
      }
    },

    clearError: () => set({ error: null }),

    clearLoading: () => set({ loading: false }),

    hydrateUserProfile: profile => {
      set(state => {
        if (!state.user) {
          return state;
        }

        const definedEntries = Object.entries(profile).filter(([, value]) => value !== undefined);
        if (definedEntries.length === 0) {
          return state;
        }

        return {
          ...state,
          user: {
            ...state.user,
            ...(Object.fromEntries(definedEntries) as Partial<AuthenticatedUser>),
          },
        };
      });
    },
  };
});

export const selectIsAuthenticated = (state: AuthState) => state.isAuthenticated;
export const selectAuthUser = (state: AuthState) => state.user;
export const selectSession = (state: AuthState) => state.session;

export const useAuthSession = () => {
  const user = useAuthStore(selectAuthUser);
  const isAuthenticated = useAuthStore(selectIsAuthenticated);
  const session = useAuthStore(selectSession);

  return {
    user,
    isAuthenticated,
    session,
  };
};
