package com.castlabs.sdk.demos;

import android.app.Activity;
import android.os.Bundle;
import android.os.Parcelable;
import androidx.annotation.NonNull;

import com.castlabs.android.adverts.AdInterface;
import com.castlabs.sdk.demos.ads.AdUi;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;

import com.castlabs.android.player.AbstractPlayerListener;
import com.castlabs.android.player.SingleControllerPlaylist;
import com.castlabs.android.player.PlayerConfig;
import com.castlabs.android.player.PlayerView;
import com.castlabs.android.player.exceptions.CastlabsPlayerException;
import com.castlabs.android.player.exceptions.PlaylistException;
import com.castlabs.sdk.playerui.PlayerControllerView;

import java.util.ArrayList;
import java.util.List;

public class ConcatenatingPlaybackDemo extends Activity {
	private static final String TAG = "SimplePlaybackDemo";
	private static final String SAVED_PLAYBACK_STATE_BUNDLE_KEY = "SAVED_PLAYBACK_STATE_BUNDLE_KEY";
	// This is the player view that we use to start playback
	private PlayerView playerView;
	private PlayerControllerView playerControllerView;
	private SingleControllerPlaylist pc;
	private AdUi adUi;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_concatenating_playlist_demo);

		// Get the view components from the layout
		playerView = (PlayerView) findViewById(R.id.player_view);
		// Get the controller view
		playerControllerView = (PlayerControllerView) findViewById(R.id.player_controls);
		playerControllerView.showExtendedTrackInfo(true);
		playerControllerView.allowMultiTrackVideoSelection(true);

		// Set up Previous and Next button listeners
		findViewById(R.id.buttonPrevious).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				pc.previousItem();
			}
		});

		findViewById(R.id.buttonNext).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				pc.nextItem();
			}
		});
		// Here we create the SingleControllerPlaylist
		pc = new SingleControllerPlaylist(this, new PlaylistListenerImpl());

		// Optional: Reset bandwidth estimation on item transition
		pc.setBandwidthResetMode(
			SingleControllerPlaylist.BANDWIDTH_RESET_MODE_AUTOMATIC_ITEM_TRANSITION
			| SingleControllerPlaylist.BANDWIDTH_RESET_MODE_EXPLICIT_ITEM_CHANGE);

		// (Optional) Custom Ad Ui rendering
		//setupCustomAdsUI();

		// Optional: set Playlist wrap around
		pc.setWrapAroundPlaylistEdges(true);

		// Subscribe for the playback errors
		pc.addPlayerListener(new PlayerListenerImpl());
		// Set it as the PlayerController in our PlayerView
		playerView.setPlayerController(pc);

		try {
			if (savedInstanceState == null) {
				Parcelable[] configs;

				Log.d(TAG, "Opening playback from intent bundle");
				// This demo assumes that you send an intent to this Activity that contains the
				// playback information.
				if (getIntent() != null) {
					configs = getIntent().getExtras().getParcelableArray(MainActivity.INTENT_PLAYLIST);
					if (configs == null) {
						throw new IllegalArgumentException("Expected an ArrayList of PlayerConfig for INTENT_PLAYLIST key.");
					}
					// Start playback
					pc.open(configs);
				} else {
					Snackbar.make(playerView, "No intent specified", Snackbar.LENGTH_INDEFINITE).show();
				}
			} else {
				Log.d(TAG, "Opening playback from saved state");
				// Start playback
				Bundle playbackBundle = savedInstanceState.getBundle(SAVED_PLAYBACK_STATE_BUNDLE_KEY);
				if (playbackBundle == null) {
					throw new IllegalArgumentException("Expected saved Playback state");
				}
				pc.openState(playbackBundle);
			}
		} catch (IllegalArgumentException e) {
			Log.e(TAG, "Error while opening player: " + e.getMessage(), e);
			Snackbar.make(playerView, "Error while opening player: " + e.getMessage(),
					Snackbar.LENGTH_INDEFINITE).show();
		}
	}

	@Override
	public boolean dispatchKeyEvent(KeyEvent event) {
		boolean handled = false;
		if (playerControllerView != null && playerControllerView.getVisibility() == View.VISIBLE) {
			handled = playerControllerView.dispatchKeyEvent(event);
		} else { // Not visible
			if (event.getAction() == KeyEvent.ACTION_DOWN) {
				handled = playerControllerView.dispatchKeyEvent(event);
			}
		}
		return handled || super.dispatchKeyEvent(event);
	}

	// Delegate the onStart event to the player views lifecycle delegate.
	// The delegate will make sure that the screen safer will be disabled and
	// the display will not go to sleep
	@Override
	protected void onStart() {
		super.onStart();
		playerView.getLifecycleDelegate().start(this);
		// Optional for custom ad ui rendering
		if (adUi != null) {
			adUi.bind(playerView.getPlayerController());
		}
	}

	// Delegate the onResume event to the player views lifecycle delegate.
	// The delegate ensures that the player recovers from a saved state. This needs to
	// be implemented to ensure the the user can for example go to the home screen and
	// come back to this activity.
	@Override
	protected void onResume() {
		super.onResume();

		// Set it as the PlayerController in our PlayerView
		playerView.setPlayerController(pc);

		// Bind the controller view
		playerControllerView.bind(playerView);

		playerView.getLifecycleDelegate().resume();
	}

	// Delegate the onStop event to the player views lifecycle delegate.
	// We release the player when the activity is stopped. This will release all the player
	// resources and save the current playback state. Saving the state is required so the
	// onResume callback can recover properly.
	@Override
	protected void onStop() {
		super.onStop();
		// Optional for custom ad ui rendering
		if (adUi != null) {
			adUi.unbind();
		}

		// Unbind the player controller view
		playerControllerView.unbind();
		playerView.getLifecycleDelegate().releasePlayer(false);
	}

	// Save the playback state when the activity is destroyed in order to correctly
	// resume after the activity is re-created again i.e. onCreate is called
	@Override
	public void onSaveInstanceState(Bundle outState) {
		// Save the playlist state in order to re-open the playlist upon activity being destroyed
		Bundle savedStateBundle = new Bundle();
		playerView.getPlayerController().saveState(savedStateBundle);
		outState.putBundle(SAVED_PLAYBACK_STATE_BUNDLE_KEY, savedStateBundle);

		super.onSaveInstanceState(outState);
	}

	private void setupCustomAdsUI() {
		AdInterface adInterface = playerView.getPlayerController().getAdInterface();
		adUi = new AdUi(this);
		adUi.setApi(adInterface.getAdApi());
		playerView.addView(adUi);
	}

	private static final class PlaylistListenerImpl extends SingleControllerPlaylist.AbstractPlaylistListener {
		@Override
		public void onItemChange(@NonNull PlayerConfig config) {
			Log.d(TAG, "onItemChange: " + config.contentUrl);
		}
	}

	/**
	 * Example player listener that demonstrates how to handle playlist errors.
	 * <p>
	 * There are two main scenarios:
	 * <ol>
	 *   <li><b>Prefetch Warning</b>: When an item fails to load during background prefetch,
	 *       a WARNING is raised. The app can replace the failing item before playback reaches it.</li>
	 *   <li><b>Fatal Error</b>: When the player tries to transition to a failed item (either
	 *       because the app didn't handle the warning, or because the user navigated directly),
	 *       a FATAL error is raised. The app must remove the item and reopen playback.</li>
	 * </ol>
	 */
	private final class PlayerListenerImpl extends AbstractPlayerListener {
		@Override
		public void onError(@NonNull CastlabsPlayerException error) {
			// Check if this is a playlist-related error
			if (error.getCause() instanceof PlaylistException) {
				handlePlaylistError(error.getSeverity(), (PlaylistException) error.getCause());
				return;
			}

			// Handle other errors
			if (error.getSeverity() == CastlabsPlayerException.SEVERITY_ERROR) {
				Snackbar.make(playerView, "Error while opening player: " + error.getMessage(),
						BaseTransientBottomBar.LENGTH_INDEFINITE).show();
			} else {
				Log.e(TAG, "Error while opening player: " + error.getMessage());
			}
		}

		private void handlePlaylistError(@CastlabsPlayerException.Severity int severity,
										 PlaylistException error) {
			final int failedIndex = error.getFailedItemIndex();
			final int currentIndex = error.getCurrentItemIndex();
			final int playlistSize = error.getPlaylistSnapshot().size();
			final PlayerConfig failedConfig = error.getFailedConfig();

			Log.e(TAG, "Playlist error: failedIndex=" + failedIndex
					+ ", currentIndex=" + currentIndex
					+ ", playlistSize=" + playlistSize
					+ ", failedUrl=" + failedConfig.contentUrl);

			if (severity == CastlabsPlayerException.SEVERITY_WARNING) {
				// Prefetch warning - replace the failing item before it's reached
				handlePrefetchWarning(error, failedIndex, failedConfig);
			} else {
				// Fatal error - remove offending item and reopen playback
				handleFatalPlaylistError(error, failedIndex, failedConfig);
			}
		}

		/**
		 * Handle prefetch warning by replacing the failing item.
		 * <p>
		 * When an item fails to load during prefetch (while another item is playing),
		 * we receive a WARNING. This gives us the opportunity to replace the failing
		 * item with a fallback before the user reaches it.
		 */
		private void handlePrefetchWarning(PlaylistException error, int failedIndex, PlayerConfig failedConfig) {
			Log.w(TAG, "Prefetch warning: Item at index " + failedIndex + " failed to load");

			// Show notification to user
			Snackbar.make(playerView,
					"Item " + (failedIndex + 1) + " unavailable, replacing with fallback...",
					Snackbar.LENGTH_SHORT).show();

			// Create a fallback config (e.g., a slate video or alternative content)
			PlayerConfig fallbackConfig = createFallbackConfig(failedConfig);

			// Remove the failing item and add the fallback at the same position
			// Note: The playlist will handle this gracefully
			pc.removeItem(failedConfig);
			pc.addItem(failedIndex, fallbackConfig);

			Log.i(TAG, "Replaced failing item with fallback at index " + failedIndex);
		}

		/**
		 * Handle fatal error by removing the offending item and reopening.
		 * <p>
		 * This occurs when:
		 * - The Playlist didn't prefetch, or the app didn't handle the prefetch warning, and the player tried to transition
		 * - The user navigated directly to an item that couldn't be loaded
		 * <p>
		 * In this case, the player has been released. We need to rebuild the playlist
		 * without the failing item and reopen playback.
		 */
		private void handleFatalPlaylistError(PlaylistException error, int failedIndex, PlayerConfig failedConfig) {
			Log.e(TAG, "Fatal playlist error: Item at index " + failedIndex + " caused playback to fail");

			// Show notification to user
			Snackbar.make(playerView,
					"Playback failed. Removing problematic item and resuming...",
					Snackbar.LENGTH_SHORT).show();

			// Get the playlist snapshot from the error (player is already released at this point)
			List<PlayerConfig> playlistSnapshot = error.getPlaylistSnapshot();

			// Build a new playlist without the failing item, replacing it with a fallback
			List<PlayerConfig> newPlaylist = new ArrayList<>();
			for (int i = 0; i < playlistSnapshot.size(); i++) {
				PlayerConfig config = playlistSnapshot.get(i);
				if (i == failedIndex) {
					// Replace the failing item with a fallback
					newPlaylist.add(createFallbackConfig(config));
				} else {
					newPlaylist.add(config);
				}
			}

			try {
				pc.open(failedIndex, newPlaylist);
				Log.i(TAG, "Reopened playlist without failing item, starting at index " + failedIndex);
			} catch (Exception e) {
				Log.e(TAG, "Failed to reopen playlist", e);
				Snackbar.make(playerView, "Unable to recover playback",
						BaseTransientBottomBar.LENGTH_INDEFINITE).show();
			}
		}

		/**
		 * Creates a fallback PlayerConfig to replace a failing item.
		 * In a real app, this could be:
		 * - A slate/placeholder video
		 * - An alternative source for the same content
		 * - Content from a CDN fallback
		 */
		private PlayerConfig createFallbackConfig(PlayerConfig failedConfig) {
			// Example: Use a known-good fallback URL
			// In production, you might have a mapping of content IDs to fallback URLs
			String fallbackUrl = "http://demo.cf.castlabs.com/media/bbb_abr/Manifest.mpd";

			return new PlayerConfig.Builder(failedConfig)
					.contentUrl(fallbackUrl)
					.get();
		}
	}
}
