import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { enqueueMixdown } from '../../helpers/rest';

// Channel group IDs for track filtering (from globals.js ChannelGroupIds)
const MEDIA_TRACK_GROUP = 6; // ChannelGroupIds.MediaTrackGroup

// Async thunks for media actions
export const openBackingTrack = createAsyncThunk(
  'media/openBackingTrack',
  async ({ file, jamClient }, { rejectWithValue }) => {
    try {
      // Open the backing track file via native client
      await jamClient.SessionOpenBackingTrackFile(file, false);

      // Get track info from native client to populate Redux state
      const allTracks = await jamClient.SessionGetAllControlState(true);

      // Extract backing track data (same pattern as useTrackHelpers.getBackingTracks)
      const backingTracks = [];
      for (const track of allTracks) {
        if (track.group_id === MEDIA_TRACK_GROUP &&
            track.media_type === 'BackingTrack' &&
            !track.managed) {
          backingTracks.push({
            id: track.persisted_track_id,
            rid: track.rid,
            filename: track.filename,
            // Compute shortFilename from full path
            shortFilename: track.filename ? track.filename.split('/').pop().split('\\').pop() : 'Audio File'
          });
        }
      }

      console.log('[openBackingTrack] Got backing tracks from native client:', backingTracks);

      return { file, backingTracks };
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

export const loadJamTrack = createAsyncThunk(
  'media/loadJamTrack',
  async ({ jamTrack, mixdownId = null, autoPlay = false, jamClient, jamServer }, { dispatch, rejectWithValue }) => {
    try {
      // Build fqId
      const sampleRate = await jamClient.GetSampleRate();
      const fqId = `${jamTrack.id}-${sampleRate === 48 ? '48' : '44'}`;

      // Check sync state
      const trackDetail = await jamClient.JamTrackGetTrackDetail(fqId);

      // If not synchronized, trigger download
      if (!trackDetail || !trackDetail.key_state || trackDetail.key_state !== 'AVAILABLE') {
        await dispatch(downloadJamTrack({ jamTrack, mixdownId, fqId, jamClient, jamServer })).unwrap();
      }

      // Load JMEP if present
      if (jamTrack.jmep) {
        await jamClient.JamTrackLoadJmep(fqId, jamTrack.jmep);
      }

      // Play with fqId (not jamTrack.id)
      const result = await jamClient.JamTrackPlay(fqId);
      if (!result) {
        throw new Error('Unable to play JamTrack');
      }

      return { jamTrack, fqId, result };
    } catch (error) {
      return rejectWithValue(error.message || error);
    }
  }
);

export const downloadJamTrack = createAsyncThunk(
  'media/downloadJamTrack',
  async ({ jamTrack, mixdownId, fqId, jamClient, jamServer }, { dispatch, rejectWithValue, getState }) => {
    try {

      // Get client sample rate for package selection (pickMyPackage logic)
      const rawSampleRate = await jamClient.GetSampleRate();

      // Normalize sample rate (jamClient returns float like 44.099998474121094 or 48.0)
      // Round to nearest standard rate: 44 or 48 kHz
      const sampleRate = rawSampleRate >= 46 ? 48 : 44;

      // Use mixdowns from jamTrack object (fetched from REST API)
      // jamClient.JamTrackGetMixdowns returns a different structure without packages
      const mixdowns = jamTrack.mixdowns;

      if (!mixdowns || mixdowns.length === 0) {
        throw new Error('No mixdowns available for this JamTrack');
      }

      // Find the mixdown (use selected mixdownId if provided, else use first)
      const mixdown = mixdownId
        ? mixdowns.find(m => m.id === mixdownId) || mixdowns[0]
        : mixdowns[0];

      if (!mixdown) {
        throw new Error('Selected mixdown not found');
      }

      if (!mixdown.packages || mixdown.packages.length === 0) {
        throw new Error(`Mixdown "${mixdown.name}" has no packages available`);
      }

      // pickMyPackage logic with fallback strategy
      // CRITICAL: Sample rate MUST match - native client cannot play mismatched rates
      // Preference order for matching sample rate:
      // 1. ogg + jkz encryption (ideal for security)
      // 2. ogg + any/no encryption (preferred format)
      // 3. any format (mp3, etc.)

      let compatiblePackage = null;

      // Try 1: Ideal package (ogg, jkz, matching sample rate)
      compatiblePackage = mixdown.packages.find(pkg =>
        pkg.file_type === 'ogg' &&
        pkg.encrypt_type === 'jkz' &&
        pkg.sample_rate === sampleRate
      );

      // Try 2: ogg with any encryption, matching sample rate
      if (!compatiblePackage) {
        compatiblePackage = mixdown.packages.find(pkg =>
          pkg.file_type === 'ogg' &&
          pkg.sample_rate === sampleRate
        );
      }

      // Try 3: Any format with matching sample rate
      if (!compatiblePackage) {
        compatiblePackage = mixdown.packages.find(pkg =>
          pkg.sample_rate === sampleRate
        );
      }

      // No fallback to different sample rates - client cannot play mismatched rates
      if (!compatiblePackage) {
        const availableRates = mixdown.packages.map(p => p.sample_rate).join(', ');
        throw new Error(`No package available for sample rate ${sampleRate}kHz. Available rates: ${availableRates}kHz. Try restarting your audio interface or selecting a different sample rate.`);
      }

      const packageId = compatiblePackage.id;

      // ========== PACKAGING PHASE ==========
      // Before downloading, package must be created/signed on server
      // This matches legacy download_jamtrack.js.coffee behavior

      dispatch(setDownloadState({
        jamTrackId: jamTrack.id,
        mixdownId,
        fqId,
        packageId,
        state: 'packaging',
        progress: 0,
        packaging_steps: 0,
        current_packaging_step: 0,
        signing_state: null
      }));

      // Enqueue mixdown for server-side packaging
      // Backend expects mixdown ID + package attributes, not package ID
      console.log(`[JamTrack] Enqueueing mixdown ${mixdownId} for packaging (file_type: ${compatiblePackage.file_type}, encrypt_type: ${compatiblePackage.encrypt_type}, sample_rate: ${sampleRate})`);
      try {
        await enqueueMixdown({
          id: mixdownId, // mixdown ID, not package ID
          file_type: compatiblePackage.file_type,
          encrypt_type: compatiblePackage.encrypt_type,
          sample_rate: sampleRate
        });
      } catch (err) {
        console.error('[JamTrack] Failed to enqueue mixdown:', err);
        // Handle both Error objects and Response objects (from apiFetch)
        const errorMessage = err?.message || err?.statusText || `HTTP ${err?.status}` || 'Unknown error';
        throw new Error(`Failed to start packaging: ${errorMessage}`);
      }

      // Subscribe to WebSocket notifications for packaging progress
      // WebSocket gateway will send SUBSCRIPTION_MESSAGE updates
      // Handler in useSessionWebSocket.js will dispatch setDownloadState updates
      console.log(`[JamTrack] Subscribing to packaging notifications for package ${packageId}`);

      if (!jamServer || !jamServer.subscribe) {
        throw new Error('WebSocket connection not available');
      }

      const subscribed = jamServer.subscribe('mixdown', packageId);
      if (!subscribed) {
        throw new Error('Failed to subscribe to packaging notifications');
      }

      // Cleanup function for unsubscribing
      const unsubscribeFromPackaging = () => {
        if (jamServer && jamServer.unsubscribe) {
          jamServer.unsubscribe('mixdown', packageId);
        }
      };

      // Wait for packaging to complete (signing_state === 'SIGNED')
      // WebSocket notifications will update downloadState as packaging progresses
      console.log('[JamTrack] Waiting for package to be signed by server...');

      const waitForPackaging = () => {
        return new Promise((resolve, reject) => {
          const timeout = setTimeout(() => {
            // Unsubscribe on timeout
            unsubscribeFromPackaging();
            reject(new Error('Packaging timeout after 60 seconds'));
          }, 60000); // 60 second timeout

          const checkInterval = setInterval(() => {
            const state = getState();
            const { signing_state, state: downloadStateValue } = state.media.downloadState;

            // Check for success
            if (signing_state === 'SIGNED') {
              clearTimeout(timeout);
              clearInterval(checkInterval);
              console.log('[JamTrack] Package signed successfully, proceeding to download');
              // Unsubscribe on success
              unsubscribeFromPackaging();
              resolve();
            }

            // Check for errors
            if (signing_state === 'ERROR' || signing_state === 'SIGNING_TIMEOUT' ||
                signing_state === 'QUEUED_TIMEOUT' || signing_state === 'QUIET_TIMEOUT') {
              clearTimeout(timeout);
              clearInterval(checkInterval);
              // Unsubscribe on error
              unsubscribeFromPackaging();
              reject(new Error(`Packaging failed with state: ${signing_state}`));
            }

            // Check if download state shows error
            if (downloadStateValue === 'error') {
              clearTimeout(timeout);
              clearInterval(checkInterval);
              // Unsubscribe on error
              unsubscribeFromPackaging();
              reject(new Error('Packaging failed'));
            }
          }, 500); // Check every 500ms
        });
      };

      await waitForPackaging();

      // ========== DOWNLOAD PHASE ==========
      // Package is now signed and ready to download

      dispatch(setDownloadState({
        state: 'downloading',
        progress: 0
      }));

      // Set up global callbacks for native client
      window.jamTrackDownloadProgress = (percent) => {
        dispatch(setDownloadState({ progress: percent }));
      };

      window.jamTrackDownloadSuccess = () => {
        dispatch(setDownloadState({ state: 'keying' }));
      };

      window.jamTrackDownloadFail = (error) => {
        dispatch(setDownloadState({ state: 'error', error: { type: 'download', message: error } }));
      };

      // Initiate download (callbacks as string names)
      await jamClient.JamTrackDownload(
        jamTrack.id,
        packageId,
        jamTrack.userId,
        'jamTrackDownloadProgress',
        'jamTrackDownloadSuccess',
        'jamTrackDownloadFail'
      );

      // Request keys
      await jamClient.JamTrackKeysRequest();

      dispatch(setDownloadState({ state: 'synchronized' }));
      return { jamTrackId: jamTrack.id, fqId };
    } catch (error) {
      dispatch(setDownloadState({ state: 'error', error: { type: 'download', message: error.message } }));
      return rejectWithValue(error.message || error);
    }
  }
);

export const checkJamTrackSync = createAsyncThunk(
  'media/checkJamTrackSync',
  async ({ jamTrack, jamClient }, { rejectWithValue }) => {
    try {
      const sampleRate = await jamClient.GetSampleRate();
      const fqId = `${jamTrack.id}-${sampleRate === 48 ? '48' : '44'}`;
      const trackDetail = await jamClient.JamTrackGetTrackDetail(fqId);
      return { fqId, trackDetail, isSynchronized: trackDetail && trackDetail.key_state === 'AVAILABLE' };
    } catch (error) {
      return rejectWithValue(error.message || error);
    }
  }
);

export const loadJMEP = createAsyncThunk(
  'media/loadJMEP',
  async ({ jamTrack, fqId, jamClient }, { rejectWithValue }) => {
    try {
      if (!jamTrack.jmep) {
        return { loaded: false };
      }
      await jamClient.JamTrackLoadJmep(fqId, jamTrack.jmep);
      return { loaded: true, fqId };
    } catch (error) {
      return rejectWithValue(error.message || error);
    }
  }
);

export const seekJamTrack = createAsyncThunk(
  'media/seekJamTrack',
  async ({ fqId, positionMs, jamClient }, { getState, rejectWithValue }) => {
    try {
      // Apply UAT-003 fix pattern: if paused, store pending seek
      const { media } = getState();
      if (media.jamTrackState.isPaused) {
        // Store pending seek, will be applied on resume
        return { pendingSeek: positionMs };
      }

      await jamClient.JamTrackSeekMs(fqId, positionMs);
      return { positionMs };
    } catch (error) {
      return rejectWithValue(error.message || error);
    }
  }
);

export const closeJamTrack = createAsyncThunk(
  'media/closeJamTrack',
  async ({ fqId, jamClient }, { dispatch, rejectWithValue }) => {
    try {
      // Stop playback if playing
      const positionStr = await jamClient.JamTrackGetPositionMs(fqId);
      const position = parseInt(positionStr, 10);
      if (position > 0) {
        await jamClient.JamTrackStop(fqId);
      }

      // Clear Redux state
      dispatch(clearJamTrackState());
      dispatch(clearDownloadState());

      return { closed: true };
    } catch (error) {
      return rejectWithValue(error.message || error);
    }
  }
);

export const closeMedia = createAsyncThunk(
  'media/closeMedia',
  async ({ force = false, jamClient }, { rejectWithValue }) => {
    try {
      await jamClient.SessionCloseMedia(force);
      return null;
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

const initialState = {
  // Media arrays (resolved/enriched data from mixer system)
  // Format matches useMixerHelper resolved data structures
  backingTracks: [], // [{ isOpener, shortFilename, instrumentIcon, photoUrl, showLoop, track, mixers }]
  jamTracks: [], // [{ name, trackName, part, isOpener, instrumentIcon, track, mixers }]
  recordedTracks: [], // [{ recordingName, isOpener, userName, instrumentIcon, track, mixers }]

  // JamTrack state (real-time playback state from native client)
  jamTrackState: {
    isPlaying: false,
    isPaused: false,
    currentPositionMs: 0,
    durationMs: 0,
    selectedMixdownId: null,
    playbackMode: null, // 'master' | 'custom-mix' | 'stem'
    lastUpdate: null
  },

  // Download/sync state machine
  downloadState: {
    jamTrackId: null,
    mixdownId: null,
    fqId: null,
    packageId: null, // Package being packaged/downloaded
    state: 'idle', // 'idle' | 'checking' | 'packaging' | 'downloading' | 'keying' | 'synchronized' | 'error'
    progress: 0,
    currentStep: 0,
    totalSteps: 0,
    // Packaging-specific fields (from legacy JamTrackStore)
    packaging_steps: 0, // Total packaging steps
    current_packaging_step: 0, // Current step in packaging
    signing_state: null, // 'SIGNING_TIMEOUT' | 'QUEUED_TIMEOUT' | 'QUIET_TIMEOUT' | 'ERROR' | 'SIGNED' | null
    error: null
  },

  // Loading states for async operations
  loading: {
    backingTrack: false,
    jamTrack: false,
    closing: false
  },

  error: null
};

export const mediaSlice = createSlice({
  name: 'media',
  initialState,
  reducers: {
    // Set resolved media data (called from useMixerHelper after processing)
    setBackingTracks: (state, action) => {
      state.backingTracks = action.payload;
    },

    setJamTracks: (state, action) => {
      state.jamTracks = action.payload;
    },

    setRecordedTracks: (state, action) => {
      state.recordedTracks = action.payload;
    },

    // JamTrack state updates (from WebSocket JAM_TRACK_CHANGES messages)
    updateJamTrackState: (state, action) => {
      state.jamTrackState = { ...state.jamTrackState, ...action.payload };
    },

    setJamTrackState: (state, action) => {
      state.jamTrackState = action.payload;
    },

    clearJamTrackState: (state) => {
      state.jamTrackState = {
        isPlaying: false,
        isPaused: false,
        currentPositionMs: 0,
        durationMs: 0,
        selectedMixdownId: null,
        playbackMode: null,
        lastUpdate: null
      };
    },

    // Download state management
    setDownloadState: (state, action) => {
      state.downloadState = { ...state.downloadState, ...action.payload };
    },

    clearDownloadState: (state) => {
      state.downloadState = {
        jamTrackId: null,
        mixdownId: null,
        fqId: null,
        packageId: null,
        state: 'idle',
        progress: 0,
        currentStep: 0,
        totalSteps: 0,
        packaging_steps: 0,
        current_packaging_step: 0,
        signing_state: null,
        error: null
      };
    },

    // Clear all media (called on session end or media close)
    clearAllMedia: (state) => {
      state.backingTracks = [];
      state.jamTracks = [];
      state.recordedTracks = [];
      state.jamTrackState = {
        isPlaying: false,
        isPaused: false,
        currentPositionMs: 0,
        durationMs: 0,
        selectedMixdownId: null,
        playbackMode: null,
        lastUpdate: null
      };
      state.downloadState = {
        jamTrackId: null,
        mixdownId: null,
        fqId: null,
        state: 'idle',
        progress: 0,
        currentStep: 0,
        totalSteps: 0,
        error: null
      };
      state.error = null;
    }
  },

  extraReducers: (builder) => {
    builder
      // Open backing track
      .addCase(openBackingTrack.pending, (state) => {
        state.loading.backingTrack = true;
        state.error = null;
      })
      .addCase(openBackingTrack.fulfilled, (state) => {
        state.loading.backingTrack = false;
        // Note: backingTracks array updated by MIXER_CHANGES WebSocket message
        // which triggers useMixerHelper to resolve and dispatch setBackingTracks
      })
      .addCase(openBackingTrack.rejected, (state, action) => {
        state.loading.backingTrack = false;
        state.error = action.payload;
      })

      // Load jam track
      .addCase(loadJamTrack.pending, (state) => {
        state.loading.jamTrack = true;
        state.downloadState.state = 'checking';
        state.error = null;
      })
      .addCase(loadJamTrack.fulfilled, (state) => {
        state.loading.jamTrack = false;
        state.downloadState.state = 'synchronized';
        // Note: jamTracks array updated by MIXER_CHANGES WebSocket message
      })
      .addCase(loadJamTrack.rejected, (state, action) => {
        state.loading.jamTrack = false;
        state.downloadState.state = 'error';
        state.downloadState.error = { message: action.payload };
        state.error = action.payload;
      })

      // Download jam track
      .addCase(downloadJamTrack.pending, (state) => {
        state.loading.jamTrack = true;
        state.error = null;
      })
      .addCase(downloadJamTrack.fulfilled, (state) => {
        state.loading.jamTrack = false;
      })
      .addCase(downloadJamTrack.rejected, (state, action) => {
        state.loading.jamTrack = false;
        state.error = action.payload;
      })

      // Check jam track sync
      .addCase(checkJamTrackSync.pending, (state) => {
        state.downloadState.state = 'checking';
        state.error = null;
      })
      .addCase(checkJamTrackSync.fulfilled, (state, action) => {
        state.downloadState.state = action.payload.isSynchronized ? 'synchronized' : 'idle';
      })
      .addCase(checkJamTrackSync.rejected, (state, action) => {
        state.downloadState.state = 'error';
        state.downloadState.error = { message: action.payload };
        state.error = action.payload;
      })

      // Load JMEP
      .addCase(loadJMEP.pending, (state) => {
        state.error = null;
      })
      .addCase(loadJMEP.fulfilled, (state) => {
        // JMEP loaded successfully
      })
      .addCase(loadJMEP.rejected, (state, action) => {
        state.error = action.payload;
      })

      // Seek jam track
      .addCase(seekJamTrack.pending, (state) => {
        state.error = null;
      })
      .addCase(seekJamTrack.fulfilled, (state, action) => {
        if (action.payload.positionMs !== undefined) {
          state.jamTrackState.currentPositionMs = action.payload.positionMs;
        }
      })
      .addCase(seekJamTrack.rejected, (state, action) => {
        state.error = action.payload;
      })

      // Close jam track
      .addCase(closeJamTrack.pending, (state) => {
        state.loading.closing = true;
      })
      .addCase(closeJamTrack.fulfilled, (state) => {
        state.loading.closing = false;
      })
      .addCase(closeJamTrack.rejected, (state, action) => {
        state.loading.closing = false;
        state.error = action.payload;
      })

      // Close media
      .addCase(closeMedia.pending, (state) => {
        state.loading.closing = true;
      })
      .addCase(closeMedia.fulfilled, (state) => {
        state.loading.closing = false;
        // Clear all media data
        state.backingTracks = [];
        state.jamTracks = [];
        state.recordedTracks = [];
        state.jamTrackState = {
          isPlaying: false,
          isPaused: false,
          currentPositionMs: 0,
          durationMs: 0,
          selectedMixdownId: null,
          playbackMode: null,
          lastUpdate: null
        };
        state.downloadState = {
          jamTrackId: null,
          mixdownId: null,
          fqId: null,
          state: 'idle',
          progress: 0,
          currentStep: 0,
          totalSteps: 0,
          error: null
        };
      })
      .addCase(closeMedia.rejected, (state, action) => {
        state.loading.closing = false;
        state.error = action.payload;
      });
  }
});

export const {
  setBackingTracks,
  setJamTracks,
  setRecordedTracks,
  updateJamTrackState,
  setJamTrackState,
  clearJamTrackState,
  setDownloadState,
  clearDownloadState,
  clearAllMedia
} = mediaSlice.actions;

export default mediaSlice.reducer;

// Cleanup function for JamTrack download callbacks
// Called on component unmount to prevent memory leaks from orphaned window globals
// Pattern matches useRecordingHelpers.js cleanup approach
export const cleanupJamTrackCallbacks = () => {
  if (typeof window !== 'undefined') {
    delete window.jamTrackDownloadProgress;
    delete window.jamTrackDownloadSuccess;
    delete window.jamTrackDownloadFail;
  }
};

// Selectors
export const selectBackingTracks = (state) => state.media.backingTracks;
export const selectJamTracks = (state) => state.media.jamTracks;
export const selectRecordedTracks = (state) => state.media.recordedTracks;
export const selectJamTrackState = (state) => state.media.jamTrackState;
export const selectDownloadState = (state) => state.media.downloadState;
export const selectMediaLoading = (state) => state.media.loading;
export const selectMediaError = (state) => state.media.error;
