import type { IApiResponse, IApiDataResponse, IMovie, IMovieDetails, IPaginatedResponse } from "@autoplex-api/request";
import { ActionTree } from "vuex";
import { Actions } from "./generics";
import { IState } from "./state";
import { Mutation, MutationsTypes } from "./mutations";
import router from "../routes";
import { GettersTypes } from "./getters";

// Payload types
type IRegisterPayload    = { token: string, name: string, email: string, password: string, retypePassword: string };
type ILoginPayload       = { email: string, password: string, remember: boolean };
type IGetPayload         = { path: string, useAuth?: boolean, options?: RequestInit };
type IPostPayload        = { path: string, body?: any, useAuth?: boolean, options?: RequestInit };

// Result types
type IFormErrors = { [field: string]: string[] };

/**
 * The available actions te perform
 */
export enum Action {
	// RESTful Generics
	Fetch  = "FETCH",
	Get    = "GET",
	Post   = "POST",
	Put    = "PUT",
	Delete = "DELETE",

	// Authentication
	AuthRegister = "AUTH_REGISTER",
	AuthLogin    = "AUTH_LOGIN",
	AuthForget   = "AUHT_FORGET",
	AuthLoad     = "AUTH_LOAD",

	// Movies Methods
	ActiveMovieRequests = "ACTIVE_MOVIE_REQUESTS",
	MovieDetails        = "MOVIE_DETAILS",
	CancelMovieRequest  = "CANCEL_MOVIE_REQUEST",
	CreateMovieRequest  = "CREATE_MOVIE_REQUEST",
	SearchMovies        = "SEARCH_MOVIES"
}

/**
 * The action function signatures
 */
export type ActionsTypes = {
	// RESTful Generics
	[Action.Fetch] : (payload: IGetPayload)  => Promise<any>,
	[Action.Get]   : (payload: IGetPayload)  => Promise<any>,
	[Action.Post]  : (payload: IPostPayload) => Promise<any>,
	[Action.Put]   : (payload: IPostPayload) => Promise<any>,
	[Action.Delete]: (payload: IPostPayload) => Promise<any>,

	// Authentication
	[Action.AuthRegister]: (payload: IRegisterPayload) => Promise<IFormErrors|null>,
	[Action.AuthLogin]   : (payload: ILoginPayload) => Promise<IFormErrors|null>,
	[Action.AuthForget]  : () => void,
	[Action.AuthLoad]    : () => boolean,

	// Movie Methods
	[Action.ActiveMovieRequests]: () => Promise<[number, IApiDataResponse<IMovie[]>]>,
	[Action.MovieDetails]       : (tmdbId: number | string) => Promise<[number, IApiDataResponse<IMovieDetails>]>,
	[Action.SearchMovies]       : (query: string) => Promise<[number, IPaginatedResponse<IMovie>]>,
	[Action.CancelMovieRequest] : (ticketId: number) => Promise<[number, IApiResponse]>,
	[Action.CreateMovieRequest] : (tmdbId: number | string) => Promise<[number, IApiDataResponse<{ ticketId: number }>]>
}

/**
 * The action function implementations
 */
export const actions: Actions<IState, GettersTypes, MutationsTypes, ActionsTypes> & ActionTree<IState, IState> = {

	// Generic RESTful API -------------------------------------------------------------------------

	/**
	 * Fetch request providing authentication and logout upon unauthorized requests
	 */
	async [Action.Fetch]({commit, state}, {path, useAuth = true, options = {}}) {
		if (useAuth) {
			if (state.user === null) {
				router.push({ name: "Login" });
				throw Error("Unauthorized");
			}
			options.credentials = "include";
			options.headers = Object.assign(options.headers ?? {}, {
				"Authorization": `Bearer ${state.user.token}`
			});
		}
		let response = await fetch(path, options);
		if (useAuth && response.status === 401) {
			commit(Mutation.UserForget, undefined);
			router.push({ name: "Login" });
			throw Error("Unauthorized");
		}
		return [response.status, <IApiResponse>(await response.json())];
	},

	/**
	 * Perform a generic GET request to the API
	 */
	async [Action.Get]({dispatch}, {path, useAuth, options = {}}) {
		return await dispatch(Action.Fetch, {
			path,
			useAuth,
			options: Object.assign(options, { method: "get" })
		});
	},

	/**
	 * Perform a generic POST request to the API
	 */
	async [Action.Post]({dispatch}, {path, body, useAuth, options = {}}) {
		return await dispatch(Action.Fetch, {
			path,
			useAuth,
			options: Object.assign(options, {
				method: "post",
				headers: Object.assign(
					options.headers ?? {},
					body !== undefined ? { "Content-Type": "application/json" } : {}),
				body: JSON.stringify(body)
			}),
		});
	},

	/**
	 * Perform a generic PUT request to the API
	 */
	async [Action.Put]({dispatch}, {path, body, useAuth, options = {}}) {
		return await dispatch(Action.Fetch, {
			path,
			useAuth,
			options: Object.assign(options, {
				method: "put",
				headers: Object.assign(
					options.headers ?? {},
					body !== undefined ? { "Content-Type": "application/json" } : {}),
				body: JSON.stringify(body)
			}),
		});
	},

	/**
	 * Perform a generic DELETE request to the API
	 */
	async [Action.Delete]({dispatch}, {path, body, useAuth, options = {}}) {
		return await dispatch(Action.Fetch, {
			path,
			useAuth,
			options: Object.assign(options, {
				method: "delete",
				headers: Object.assign(
					options.headers ?? {},
					body !== undefined ? { "Content-Type": "application/json" } : {}),
				body: JSON.stringify(body)
			}),
		});
	},

	// Authentication ------------------------------------------------------------------------------

	/**
	 * Register a user. Returns any errors
	 */
	async [Action.AuthRegister]({dispatch}, payload: IRegisterPayload) {
		try {
			let [status, response] = await dispatch(Action.Post, {
				path: `/auth/register`,
				useAuth: false,
				body: payload});
			if (status !== 200) {
				return response.errors ?? {};
			}
		} catch(e) {
			console.error("Error occurred during registration", e);
			throw new Error(e);
		}
		return null;
	},

	/**
	 * Authenticate the credentials of a user and log them in. Returns any errors
	 */
	async [Action.AuthLogin]({commit, dispatch}, {email, password, remember = false}) {
		try {
			let [status, response] = await dispatch(Action.Post, {
				path: `/auth/login?use_cookies=${navigator.cookieEnabled}`,
				useAuth: false,
				body: {email, password}});
			if (status !== 200) {
				if (status === 401) {
					response.errors = { "email": ["Email or password is incorrect"] };
				}
				return response.errors || {};
			}
			console.log(response);
			commit(Mutation.UserLoad, response.result.token);
			commit(Mutation.UserStore, remember);
		} catch(e) {
			console.error("Error occurred during login:", e);
			throw new Error(e);
		}
		return null;
	},

	/**
	 * Forget the login token and log the user out
	 */
	[Action.AuthForget]({commit}) {
		commit(Mutation.UserForget, undefined);
	},

	/**
	 * Load the user from local storage
	 */
	[Action.AuthLoad]({getters, commit, dispatch}) {
		let token = getters.storedToken;
		if (!token) {
			return false;
		}
		try {
			commit(Mutation.UserLoad, token);
		} catch(e) {
			dispatch(Action.AuthForget, undefined);
			return false;
		}
		return true;
	},

	// Movies --------------------------------------------------------------------------------------

	/**
	 * Get the user's active movie requests
	 */
	async [Action.ActiveMovieRequests]({dispatch}) {
		return await dispatch(Action.Get, {
			path: `/api/movie/request/active`
		});
	},

	/**
	 * Get the details of a specific movie by its TMDb ID
	 */
	async [Action.MovieDetails]({dispatch}, tmdbId: string|number) {
		return await dispatch(Action.Get, {
			path: `/api/movie/details/${tmdbId}`
		});
	},

	/**
	 * Search for movies
	 */
	async [Action.SearchMovies]({dispatch}, query: string) {
		return await dispatch(Action.Get, {
			path: `/api/movie/search?query=${encodeURI(query)}`
		});
	},

	/**
	 * Cancel a movie request
	 */
	async [Action.CancelMovieRequest]({dispatch}, ticketId: number) {
		return await dispatch(Action.Delete, {
			path: `/api/movie/request/${ticketId}`
		});
	},

	/**
	 * Create a movie request
	 */
	async [Action.CreateMovieRequest]({dispatch}, tmdbId: string|number) {
		return await dispatch(Action.Post, {
			path: `/api/movie/request/create/${tmdbId}`
		});
	}
};
