/**
 * Entity Slice Factory
 */

import {
  createSlice,
  createAsyncThunk,
  PayloadAction,
  AsyncThunk,
} from '@reduxjs/toolkit';
import axios, { AxiosError } from 'axios';
import {
  fulfilledNotify,
  rejectNotify,
  resetNotify,
} from '../helpers/notifyStateHandler';
import type {
  EntitySliceConfig,
  NotificationState,
  EntitySliceState,
} from '../types/redux';
import type { PaginatedResponse, FetchParams, ApiError } from '../types/api';
import type { BaseEntity } from '../types/entities';

// Internal state type that matches legacy structure for backward compatibility
interface InternalSliceState<T> {
  [key: string]: T[] | boolean | number | NotificationState | unknown[];
  loading: boolean;
  count: number;
  refetch: boolean;
  rolesWidgets: unknown[];
  notify: NotificationState;
}

// Type guard to check if response is paginated
function isPaginatedResponse<T>(
  response: unknown,
): response is PaginatedResponse<T> {
  return (
    typeof response === 'object' &&
    response !== null &&
    'rows' in response &&
    'count' in response
  );
}

// Type guard to check for axios error
function isAxiosError(error: unknown): error is AxiosError<ApiError> {
  return axios.isAxiosError(error);
}

// Get singular form of entity name for notifications
function getSingularName(name: string, singularName?: string): string {
  if (singularName) return singularName;
  // Simple pluralization handling - removes trailing 's'
  return name.endsWith('s') ? name.slice(0, -1) : name;
}

// Capitalize first letter
function capitalize(str: string): string {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

/**
 * Creates a Redux slice for an entity with standard CRUD operations
 *
 * @param config - Configuration for the entity slice
 * @returns Object containing the slice, reducer, and all action creators
 *
 * @example
 * ```typescript
 * const { slice, actions, reducer } = createEntitySlice<User>({
 *   name: 'users',
 *   endpoint: 'users',
 *   singularName: 'User'
 * })
 *
 * export const { fetch, create, update, deleteItem, deleteItemsByIds, uploadCsv, setRefetch } = actions
 * export default reducer
 * ```
 */
export function createEntitySlice<T extends BaseEntity>(
  config: EntitySliceConfig,
) {
  const { name, endpoint, singularName } = config;
  const displayName = capitalize(singularName || getSingularName(name));
  const pluralDisplayName = capitalize(name);

  // Create initial state
  const initialState: InternalSliceState<T> = {
    [name]: [] as T[],
    loading: false,
    count: 0,
    refetch: false,
    rolesWidgets: [],
    notify: {
      showNotification: false,
      textNotification: '',
      typeNotification: '',
    },
  };

  // Fetch thunk - gets single entity by ID or list with query
  const fetch = createAsyncThunk<
    T | PaginatedResponse<T>,
    FetchParams,
    { rejectValue: ApiError }
  >(`${name}/fetch`, async (data: FetchParams) => {
    const { id, query } = data;
    const url = `${endpoint}${query || (id ? `/${id}` : '')}`;
    const result = await axios.get<T | PaginatedResponse<T>>(url);

    if (id) {
      return result.data as T;
    }

    return {
      rows: (result.data as PaginatedResponse<T>).rows,
      count: (result.data as PaginatedResponse<T>).count,
    };
  });

  // Delete by IDs thunk - bulk delete
  const deleteItemsByIds = createAsyncThunk<
    void,
    string[],
    { rejectValue: ApiError }
  >(`${name}/deleteByIds`, async (ids: string[], { rejectWithValue }) => {
    try {
      await axios.post(`${endpoint}/deleteByIds`, { data: ids });
    } catch (error) {
      if (isAxiosError(error) && error.response) {
        return rejectWithValue(error.response.data as ApiError);
      }
      throw error;
    }
  });

  // Delete single item thunk
  const deleteItem = createAsyncThunk<void, string, { rejectValue: ApiError }>(
    `${name}/delete${capitalize(name)}`,
    async (id: string, { rejectWithValue }) => {
      try {
        await axios.delete(`${endpoint}/${id}`);
      } catch (error) {
        if (isAxiosError(error) && error.response) {
          return rejectWithValue(error.response.data as ApiError);
        }
        throw error;
      }
    },
  );

  // Create thunk
  const create = createAsyncThunk<T, Partial<T>, { rejectValue: ApiError }>(
    `${name}/create${capitalize(name)}`,
    async (data: Partial<T>, { rejectWithValue }) => {
      try {
        const result = await axios.post<T>(endpoint, { data });
        return result.data;
      } catch (error) {
        if (isAxiosError(error) && error.response) {
          return rejectWithValue(error.response.data as ApiError);
        }
        throw error;
      }
    },
  );

  // Update thunk
  const update = createAsyncThunk<
    T,
    { id: string; data: Partial<T> },
    { rejectValue: ApiError }
  >(
    `${name}/update${capitalize(name)}`,
    async (payload: { id: string; data: Partial<T> }, { rejectWithValue }) => {
      try {
        const result = await axios.put<T>(`${endpoint}/${payload.id}`, {
          id: payload.id,
          data: payload.data,
        });
        return result.data;
      } catch (error) {
        if (isAxiosError(error) && error.response) {
          return rejectWithValue(error.response.data as ApiError);
        }
        throw error;
      }
    },
  );

  // CSV upload thunk
  const uploadCsv = createAsyncThunk<
    { imported: number },
    File,
    { rejectValue: ApiError }
  >(`${name}/uploadCsv`, async (file: File, { rejectWithValue }) => {
    try {
      const formData = new FormData();
      formData.append('file', file);
      formData.append('filename', file.name);

      const result = await axios.post<{ imported: number }>(
        `${endpoint}/bulk-import`,
        formData,
        {
          headers: {
            'Content-Type': 'multipart/form-data',
          },
        },
      );
      return result.data;
    } catch (error) {
      if (isAxiosError(error) && error.response) {
        return rejectWithValue(error.response.data as ApiError);
      }
      throw error;
    }
  });

  // Create the slice
  const slice = createSlice({
    name,
    initialState,
    reducers: {
      setRefetch: (state, action: PayloadAction<boolean>) => {
        state.refetch = action.payload;
      },
      clearState: (state) => {
        (state as Record<string, unknown>)[name] = [];
        state.count = 0;
      },
    },
    extraReducers: (builder) => {
      // Fetch handlers
      builder.addCase(fetch.pending, (state) => {
        state.loading = true;
        resetNotify(state);
      });
      builder.addCase(fetch.rejected, (state, action) => {
        state.loading = false;
        rejectNotify(state, action);
      });
      builder.addCase(fetch.fulfilled, (state, action) => {
        const payload = action.payload;
        if (isPaginatedResponse<T>(payload)) {
          (state as Record<string, unknown>)[name] = payload.rows;
          state.count = payload.count;
        } else {
          (state as Record<string, unknown>)[name] = payload;
        }
        state.loading = false;
      });

      // Delete by IDs handlers
      builder.addCase(deleteItemsByIds.pending, (state) => {
        state.loading = true;
        resetNotify(state);
      });
      builder.addCase(deleteItemsByIds.fulfilled, (state) => {
        state.loading = false;
        fulfilledNotify(state, `${pluralDisplayName} has been deleted`);
      });
      builder.addCase(deleteItemsByIds.rejected, (state, action) => {
        state.loading = false;
        rejectNotify(state, action);
      });

      // Delete single item handlers
      builder.addCase(deleteItem.pending, (state) => {
        state.loading = true;
        resetNotify(state);
      });
      builder.addCase(deleteItem.fulfilled, (state) => {
        state.loading = false;
        fulfilledNotify(state, `${displayName} has been deleted`);
      });
      builder.addCase(deleteItem.rejected, (state, action) => {
        state.loading = false;
        rejectNotify(state, action);
      });

      // Create handlers
      builder.addCase(create.pending, (state) => {
        state.loading = true;
        resetNotify(state);
      });
      builder.addCase(create.fulfilled, (state) => {
        state.loading = false;
        fulfilledNotify(state, `${displayName} has been created`);
      });
      builder.addCase(create.rejected, (state, action) => {
        state.loading = false;
        rejectNotify(state, action);
      });

      // Update handlers
      builder.addCase(update.pending, (state) => {
        state.loading = true;
        resetNotify(state);
      });
      builder.addCase(update.fulfilled, (state) => {
        state.loading = false;
        fulfilledNotify(state, `${displayName} has been updated`);
      });
      builder.addCase(update.rejected, (state, action) => {
        state.loading = false;
        rejectNotify(state, action);
      });

      // Upload CSV handlers
      builder.addCase(uploadCsv.pending, (state) => {
        state.loading = true;
        resetNotify(state);
      });
      builder.addCase(uploadCsv.fulfilled, (state) => {
        state.loading = false;
        fulfilledNotify(state, `${pluralDisplayName} has been uploaded`);
      });
      builder.addCase(uploadCsv.rejected, (state, action) => {
        state.loading = false;
        rejectNotify(state, action);
      });
    },
  });

  return {
    slice,
    reducer: slice.reducer,
    actions: {
      ...slice.actions,
      fetch,
      create,
      update,
      deleteItem,
      deleteItemsByIds,
      uploadCsv,
    },
  };
}

// Export types for use in components
export type EntityActions<T extends BaseEntity> = ReturnType<
  typeof createEntitySlice<T>
>['actions'];
