import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import Cookies from "js-cookie";
import { authService } from "@/services/authService";
import { getStoredPlayerId } from "@/services/oneSignalService";
import {
  firebaseEmailSignIn,
  firebaseCreateAccount,
  signOutFromFirebase,
} from "@/services/firebaseAuthService";
import { AuthState, AuthUser, LoginPayload, LoginType, RegisterPayload } from "@/types/auth";

const COOKIE_OPTS: Cookies.CookieAttributes = { expires: 7, secure: process.env.NODE_ENV === "production", sameSite: "strict" };

function deviceFields(): { device_type: string; device_token: string } {
  return {
    device_type: "5", // 5 = Chrome Web Push (OneSignal device type enum)
    device_token: getStoredPlayerId() ?? "",
  };
}

const persistAuth = (user: AuthUser) => {
  Cookies.set("user_id", String(user.id), COOKIE_OPTS);
  Cookies.set("channel_id", user.channel_id ?? "", COOKIE_OPTS);
  if (user.stream_token) {
    Cookies.set("auth_token", user.stream_token, COOKIE_OPTS);
  }
};

const clearAuth = () => {
  Cookies.remove("user_id");
  Cookies.remove("channel_id");
  Cookies.remove("auth_token");
};

const initialState: AuthState = {
  user: null,
  isAuthenticated: false,
  isLoading: false,
  error: null,
};

function extractValidUser(
  res: { status: number; message?: string; result: AuthUser | AuthUser[] },
  fallbackMsg: string,
): AuthUser | string {
  if (res.status !== 200 && res.status !== 1) return res.message ?? fallbackMsg;
  const user: AuthUser = Array.isArray(res.result) ? res.result[0] : res.result;
  if (!user || !user.id || Number(user.id) === 0)
    return res.message ?? "Invalid credentials. Please try again.";
  return user;
}

/* ── Email / Password ──────────────────────────────────────────────────────── */
// Backend is the sole source of truth for the user's password. Firebase is
// only used afterwards to obtain a UID for chat/Firestore, so it never sees
// the user's real password — only the shared static one.
//
// If the caller already knows the Firebase UID (e.g. right after
// registerUser just created it), pass it in payload.firebase_id — it's sent
// on the very first backend call so there's no redundant Firebase sign-in or
// extra "persist it" round trip.
export const loginWithEmail = createAsyncThunk(
  "auth/loginWithEmail",
  async (payload: LoginPayload, { rejectWithValue }) => {
    try {
      // 1 & 2. Authenticate against the backend first.
      const res = await authService.login({
        ...payload,
        type: LoginType.Normal,
        ...deviceFields(),
      });
      const result = extractValidUser(res, "Login failed");
      if (typeof result === "string") return rejectWithValue(result);

      // 3. Backend succeeded — persist the session immediately.
      persistAuth(result);

      // 4. Firebase (chat-only) — never blocks login if it fails.
      try {
        let firebaseUid = payload.firebase_id || result.firebase_id;
        if (!firebaseUid) {
          // Sign in with the static password; if this email has no Firebase
          // account yet (first login under this scheme), create one instead.
          try {
            firebaseUid = await firebaseEmailSignIn(payload.email!);
          } catch {
            firebaseUid = await firebaseCreateAccount(payload.email!);
          }

          // Backend doesn't have this UID stored yet — persist it there.
          authService
            .login({ ...payload, type: LoginType.Normal, firebase_id: firebaseUid, ...deviceFields() })
            .catch((err) => console.error("[Auth] Failed to persist firebase_id on backend:", err));
        }

        return { ...result, firebase_id: firebaseUid };
      } catch (err: unknown) {
        console.error("[Auth] Firebase chat sign-in failed:", err);
        return result;
      }
    } catch (err: unknown) {
      return rejectWithValue(err instanceof Error ? err.message : "Login failed");
    }
  },
);

/* ── Google Sign-In ────────────────────────────────────────────────────────── */
// Firebase popup happens in GoogleLoginButton before this is dispatched.
// The component passes firebase_id (uid) and email/full_name from Google.
export const loginWithGoogle = createAsyncThunk(
  "auth/loginWithGoogle",
  async (payload: LoginPayload, { rejectWithValue }) => {
    try {
      const res = await authService.login({
        ...payload,
        type: LoginType.Google,
        ...deviceFields(),
      });
      const result = extractValidUser(res, "Google login failed");
      if (typeof result === "string") return rejectWithValue(result);
      persistAuth(result);
      return { ...result, firebase_id: result.firebase_id ?? payload.firebase_id };
    } catch (err: unknown) {
      return rejectWithValue(err instanceof Error ? err.message : "Google login failed");
    }
  },
);

/* ── Phone OTP ─────────────────────────────────────────────────────────────── */
// Firebase phone sign-in + OTP confirm happens in OtpPage before this is dispatched.
// The component passes firebase_id (uid) and mobile_number/country_code.
export const loginWithOTP = createAsyncThunk(
  "auth/loginWithOTP",
  async (payload: LoginPayload, { rejectWithValue }) => {
    try {
      const res = await authService.login({
        ...payload,
        type: LoginType.OTP,
        ...deviceFields(),
      });
      const result = extractValidUser(res, "OTP login failed");
      if (typeof result === "string") return rejectWithValue(result);
      persistAuth(result);
      return { ...result, firebase_id: result.firebase_id ?? payload.firebase_id };
    } catch (err: unknown) {
      return rejectWithValue(err instanceof Error ? err.message : "OTP login failed");
    }
  },
);

/* ── Sign Up ───────────────────────────────────────────────────────────────── */
export const registerUser = createAsyncThunk(
  "auth/register",
  async (payload: RegisterPayload & { password: string }, { rejectWithValue }) => {
    try {
      // 1. Backend registration is the source of truth.
      const res = await authService.register(payload);
      if (res.status !== 200 && res.status !== 1)
        return rejectWithValue(res.message ?? "Registration failed");

      // 2. Create the matching Firebase account for chat (shared static password).
      // The UID is handed back so the caller can pass it straight into
      // loginWithEmail, which sends it to the backend on the first call.
      let firebaseUid: string | undefined;
      try {
        firebaseUid = await firebaseCreateAccount(payload.email);
      } catch (err) {
        console.error("[Auth] Firebase chat account creation failed:", err);
      }

      return { firebaseUid };
    } catch (err: unknown) {
      return rejectWithValue(err instanceof Error ? err.message : "Registration failed");
    }
  },
);

/* ── Logout ────────────────────────────────────────────────────────────────── */
export const logoutUser = createAsyncThunk(
  "auth/logout",
  async (_, { dispatch }) => {
    try {
      await authService.logout(getStoredPlayerId() ?? "");
    } catch {
      // continue even if logout API fails
    } finally {
      await signOutFromFirebase();
      clearAuth();
      dispatch({ type: "RESET_APP" });
    }
  },
);

/* ── Forgot Password (backend sends the reset link — Firebase is not involved) ─ */
export const forgotPassword = createAsyncThunk(
  "auth/forgotPassword",
  async (email: string, { rejectWithValue }) => {
    try {
      const res = await authService.forgotPassword(email);
      if (res.status !== 200 && res.status !== 1)
        return rejectWithValue(res.message ?? "Request failed");
      return (res.message as string) ?? "Password reset email sent. Check your inbox.";
    } catch (err: unknown) {
      return rejectWithValue(err instanceof Error ? err.message : "Request failed");
    }
  },
);

/* ── Reset Password (backend token flow) ───────────────────────────────────── */
export const resetPassword = createAsyncThunk(
  "auth/resetPassword",
  async (
    payload: { token: string; password: string; password_confirmation: string },
    { rejectWithValue },
  ) => {
    try {
      const res = await authService.resetPassword(
        payload.token,
        payload.password,
        payload.password_confirmation,
      );
      if (res.status !== 200 && res.status !== 1)
        return rejectWithValue(res.message ?? "Reset failed");
      return res.message as string;
    } catch (err: unknown) {
      return rejectWithValue(err instanceof Error ? err.message : "Reset failed");
    }
  },
);

/* ── Slice ─────────────────────────────────────────────────────────────────── */
const authSlice = createSlice({
  name: "auth",
  initialState,
  reducers: {
    clearError: (state) => { state.error = null; },
    setError: (state, action: { payload: string }) => {
      state.error = action.payload;
      state.isLoading = false;
    },
    logout: (state) => {
      state.user = null;
      state.isAuthenticated = false;
      clearAuth();
    },
    validateAuth: (state) => {
      const id = Cookies.get("user_id");
      const isValidId = !!id && id !== "0" && id !== "" && id !== "null" && id !== "undefined";
      if (state.isAuthenticated && !isValidId) {
        state.isAuthenticated = false;
        state.user = null;
        clearAuth();
      }
    },
  },
  extraReducers: (builder) => {
    const loginFulfilled = (state: AuthState, action: { payload: AuthUser }) => {
      state.isLoading = false;
      state.user = action.payload;
      state.isAuthenticated = true;
      state.error = null;
    };
    const loginRejected = (state: AuthState, action: { payload: unknown }) => {
      state.isLoading = false;
      state.error = action.payload as string;
    };
    const loginPending = (state: AuthState) => {
      state.isLoading = true;
      state.error = null;
    };

    builder
      .addCase(loginWithEmail.pending, loginPending)
      .addCase(loginWithEmail.fulfilled, loginFulfilled)
      .addCase(loginWithEmail.rejected, loginRejected)
      .addCase(loginWithGoogle.pending, loginPending)
      .addCase(loginWithGoogle.fulfilled, loginFulfilled)
      .addCase(loginWithGoogle.rejected, loginRejected)
      .addCase(loginWithOTP.pending, loginPending)
      .addCase(loginWithOTP.fulfilled, loginFulfilled)
      .addCase(loginWithOTP.rejected, loginRejected)
      .addCase(registerUser.pending, (state) => { state.isLoading = true; state.error = null; })
      .addCase(registerUser.fulfilled, (state) => { state.isLoading = false; })
      .addCase(registerUser.rejected, (state, action) => {
        state.isLoading = false;
        state.error = action.payload as string;
      })
      .addCase(forgotPassword.pending, (state) => { state.isLoading = true; state.error = null; })
      .addCase(forgotPassword.fulfilled, (state) => { state.isLoading = false; })
      .addCase(forgotPassword.rejected, (state, action) => {
        state.isLoading = false;
        state.error = action.payload as string;
      })
      .addCase(resetPassword.pending, (state) => { state.isLoading = true; state.error = null; })
      .addCase(resetPassword.fulfilled, (state) => { state.isLoading = false; })
      .addCase(resetPassword.rejected, (state, action) => {
        state.isLoading = false;
        state.error = action.payload as string;
      });
  },
});

export const { clearError, setError, logout, validateAuth } = authSlice.actions;
export default authSlice.reducer;
