import React, { useState, useContext, createContext, useReducer, useEffect } from "react";
import Integration from "./Integration";
import * as serverApi from '../../api';
import SpotifyClientCreds from "./spotify/SpotifyClientCreds";
import * as backend from "../backend/integrations";
import { handleNotLoggedIn, NotLoggedInError } from "../backend/request";
import { useAuth } from "../useAuth";
import YoutubeMusicWebScraper from "./youtubemusic/YoutubeMusicWebScraper";

export type IntegrationState = {
    id: number,
    integration: Integration,
    properties: serverApi.CreateIntegrationRequest,
};
export type IntegrationsState = IntegrationState[] | "Loading";

export function isIntegrationState(v: any): v is IntegrationState {
    return 'id' in v && 'integration' in v && 'properties' in v;
}

export interface Integrations {
    state: IntegrationsState,
    addIntegration: (v: serverApi.CreateIntegrationRequest) => Promise<number>,
    deleteIntegration: (id: number) => Promise<void>,
    modifyIntegration: (id: number, v: serverApi.CreateIntegrationRequest) => Promise<void>,
    updateFromUpstream: () => Promise<void>,
};

export const IntegrationClasses: Record<any, any> = {
    [serverApi.IntegrationType.SpotifyClientCredentials]: SpotifyClientCreds,
    [serverApi.IntegrationType.YoutubeWebScraper]: YoutubeMusicWebScraper,
}

export function makeDefaultIntegrationProperties(type: serverApi.IntegrationType):
    serverApi.CreateIntegrationRequest {
    switch (type) {
        case serverApi.IntegrationType.SpotifyClientCredentials: {
            return {
                name: "Spotify App",
                type: type,
                details: { clientId: "" },
                secretDetails: { clientSecret: "" },
            }
        }
        case serverApi.IntegrationType.YoutubeWebScraper: {
            return {
                name: "Youtube Music Web Scraper",
                type: type,
                details: {},
                secretDetails: {},
            }
        }
        default: {
            throw new Error("Unimplemented default integration.")
        }
    }
}

export function makeIntegration(p: serverApi.CreateIntegrationRequest, id: number) {
    switch (p.type) {
        case serverApi.IntegrationType.SpotifyClientCredentials: {
            return new SpotifyClientCreds(id);
        }
        case serverApi.IntegrationType.YoutubeWebScraper: {
            return new YoutubeMusicWebScraper(id);
        }
        default: {
            throw new Error("Unimplemented integration type.")
        }
    }
}

const integrationsContext = createContext<Integrations>({
    state: [],
    addIntegration: async () => 0,
    modifyIntegration: async () => { },
    deleteIntegration: async () => { },
    updateFromUpstream: async () => { },
});

export function ProvideIntegrations(props: { children: any }) {
    const integrations = useProvideIntegrations();
    return <integrationsContext.Provider value={integrations}>{props.children}</integrationsContext.Provider>;
}

export const useIntegrations = () => {
    return useContext(integrationsContext);
};

function useProvideIntegrations(): Integrations {
    let auth = useAuth();
    enum IntegrationsActions {
        SetItem = "SetItem",
        Set = "Set",
        DeleteItem = "DeleteItem",
        AddItem = "AddItem",
    }
    let IntegrationsReducer = (state: IntegrationsState, action: any): IntegrationsState => {
        switch (action.type) {
            case IntegrationsActions.SetItem: {
                if (state !== "Loading") {
                    return state.map((item: any) => {
                        return (item.id === action.id) ? action.value : item;
                    })
                }
                return state;
            }
            case IntegrationsActions.Set: {
                return action.value;
            }
            case IntegrationsActions.DeleteItem: {
                if (state !== "Loading") {
                    const newState = [...state];
                    return newState.filter((item: any) => item.id !== action.id);
                }
                return state;
            }
            case IntegrationsActions.AddItem: {
                return [...state, action.value];
            }
            default:
                throw new Error("Unimplemented Integrations state update.")
        }
    }

    const [state, dispatch] = useReducer(IntegrationsReducer, [])

    let updateFromUpstream = async () => {
        return await backend.getIntegrations()
            .then((integrations: serverApi.ListIntegrationsResponse) => {
                dispatch({
                    type: IntegrationsActions.Set,
                    value: integrations.map((i: any) => {
                        return {
                            integration: new (IntegrationClasses[i.type])(i.id),
                            properties: { ...i },
                            id: i.id,
                        }
                    })
                });
            })
            .catch((e) => handleNotLoggedIn(auth, e));
    }

    let addIntegration = async (v: serverApi.CreateIntegrationRequest) => {
        const id = await backend.createIntegration(v).catch((e: any) => { handleNotLoggedIn(auth, e) });
        await updateFromUpstream();
        return id;
    }

    let deleteIntegration = async (id: number) => {
        await backend.deleteIntegration(id).catch((e: any) => { handleNotLoggedIn(auth, e) });
        await updateFromUpstream();
    }

    let modifyIntegration = async (id: number, v: serverApi.CreateIntegrationRequest) => {
        await backend.modifyIntegration(id, v).catch((e: any) => { handleNotLoggedIn(auth, e) });
        await updateFromUpstream();
    }

    useEffect(() => {
        if (auth.user) {
            updateFromUpstream()
        }
    }, [auth]);

    return {
        state: state,
        addIntegration: addIntegration,
        modifyIntegration: modifyIntegration,
        deleteIntegration: deleteIntegration,
        updateFromUpstream: updateFromUpstream,
    }
}