import axios, { AxiosRequestConfig } from "axios";
import { QueryClient, useMutation, useQuery, useQueryClient } from "react-query";
import { authStore } from "../stores/AuthStore";
import { mapStore } from "../stores/FilterStore";
import { API_URL, reportAxiosError } from "../utils/RequestUtils";
import { useAuth } from "./useAuth";
import { LMComment } from "./useComments";

/**
 * Interface representing a landmark object
 */
export interface Landmark {
    /**
     * The id of the landmark.
     */
    id?: string | null,
    /**
     * The rating of the landmark.
     */
    rating?: number | null,
    /**
     * The id of the user who created the landmark.
     */
    user?: string | null,
    /**
     * The longitude of the landmark's location.
     */
    longitude?: number | null,
    /**
     * The latitude of the landmark's location.
     */
    latitude?: number | null, 
    /**
     * The landmark's title.
     */
    title?: string | null,
    /**
     * The landmark's description.
     */
    description?: string | null,
    /**
     * User [comments]{@link LMComment} associated with this landmark.
     */
    comments?: LMComment[] | null,
    /**
     * [Photos]{@link LMPhoto} associated with this landmark.
     */
     photos?: LMPhoto[] | null,
    /**
     * An integer representing the type of this landmark.
     */
    landmark_type?: number | null, // for working with existing database schema, should be changed
    /**
     * A Date object representing when this landmark was created.
     */
    time?: Date | null,
}

export interface  LMPhoto {
    id: string
    landmark: string,
    image_b64: string
    height: number,
    width: number
}

export interface UseLandmarkOptions {
    userId?: string
    landmarkId?: string
    userLMPairing?: {userId?: string, landmarkId?: string}
    changingPassword?: boolean
}

/**
 * A custom hook containing [react-query]{@link https://react-query.tanstack.com/} queries and mutations and other logic related to interacting with {@link Landmark} objects.
 * @category Hooks
 * @namespace useLandmarks
 */
export const useLandmarks = (options?: UseLandmarkOptions) => {
    const { refreshAccessToken } = useAuth();

    /**
     * The local instance of the [react-query QueryClient]{@link https://react-query.tanstack.com/reference/QueryClient#_top}.
     * @memberOf useLandmarks
     */
    let queryClient: QueryClient;

    try {
        queryClient = useQueryClient()
    } catch (error) {
        console.log("Something went wrong when retrieving query client: " + error)
    }

    /**
     * The callback responsible for retrieving {@link Landmark} from the API, used by the [react-query useQuery]{@link https://react-query.tanstack.com/reference/useQuery#_top} hook.
     * * @memberOf useLandmarks
     */
    const getLandmarks = async () => {
        let url = `${API_URL}/api/landmarks/`   
        const config: AxiosRequestConfig = {
            method: 'GET',
            url: url,
            headers: { "Authorization": "Bearer " + authStore.accessToken, }
        } 

        try {
            const response = await axios(config);   
            console.log(mapStore.filterOptions)
            return response.data
        } catch (error) {
            if (error.response.status == 401) {
                try {
                    await refreshAccessToken()    
                    const response = await axios({...config, headers: { "Authorization": "Bearer " + authStore.accessToken }});   
                    return response.data
                } catch (error) {
                    // refreshAccessToken will report errors
                }
            }

            reportAxiosError('Something went wrong when retrieving landmarks', error)
            throw new Error;
        }   
        // }
        // else {
        //     return [];
        // }
    }

    /**
     * The callback responsible for retrieving photos for a {@link Landmark}, used by the [react-query useQuery]{@link https://react-query.tanstack.com/reference/useQuery#_top} hook.
     * * @memberOf useLandmarks
     */
    const getLandmark = async (landmarkId?: string) => {
        if (landmarkId) {
            let url = `${API_URL}/api/landmark/${landmarkId}`   
        
            const config: AxiosRequestConfig = {
                method: 'GET',
                url: url,
                headers: { "Authorization": "Bearer " + authStore.accessToken, } 
            }
    
            try {
                const response = await axios(config);   
                console.log(Object.keys(response.data))
                return response.data
            } catch (error) {
                if (error.response.status == 401) {
                    try {
                        await refreshAccessToken()    
                        const response = await axios({...config, headers: { "Authorization": "Bearer " + authStore.accessToken }});   
                        return response.data
                    } catch (error) {
                        // refreshAccessToken will report errors
                    }
                }
    
                reportAxiosError('Something went wrong when retrieving landmarks', error)
                throw new Error;
            }   
        }
        
        // }
        // else {
        //     return [];
        // }
    }

    const addLandmarkPhoto = async (photo: LMPhoto) => {
        if (photo) {
            let url = `${API_URL}/api/landmark/photos/`   
        
            const config: AxiosRequestConfig = {
                method: 'POST',
                url: url,
                data: photo,
                headers: { "Authorization": "Bearer " + authStore.accessToken, } 
            }
    
            try {
                const response = await axios(config);   
                return response.data
            } catch (error) {
                if (error.response.status == 401) {
                    try {
                        await refreshAccessToken()    
                        const response = await axios({...config, headers: { "Authorization": "Bearer " + authStore.accessToken }});   
                        return response.data
                    } catch (error) {
                        // refreshAccessToken will report errors
                    }
                }
    
                reportAxiosError('Something went wrong when retrieving landmarks', error)
                throw new Error;
            }   
        }
    }

    const deleteLandmarkPhoto = async (photoId: string) => {
        if (photoId) {
            let url = `${API_URL}/api/landmark/photos/${photoId}`   
        
            const config: AxiosRequestConfig = {
                method: 'DELETE',
                url: url,
                headers: { "Authorization": "Bearer " + authStore.accessToken, } 
            }
    
            try {
                const response = await axios(config);   
                return response.data
            } catch (error) {
                if (error.response.status == 401) {
                    try {
                        await refreshAccessToken()    
                        const response = await axios({...config, headers: { "Authorization": "Bearer " + authStore.accessToken }});   
                        return response.data
                    } catch (error) {
                        // refreshAccessToken will report errors
                    }
                }
    
                reportAxiosError('Something went wrong when retrieving landmarks', error)
                throw new Error;
            }   
        }
        
        // }
        // else {
        //     return [];
        // }
    }

    const checkIfRatedByUser = async (userLMPairing: {userId?: string, landmarkId?: string}) => {
        if (options?.userLMPairing) {
            if (userLMPairing?.landmarkId && userLMPairing?.userId) {
                const config: AxiosRequestConfig = {
                    method: 'GET',
                    url: `${API_URL}/api/landmark/check-rate-pairing/?landmark=${options?.userLMPairing?.landmarkId}&user=${options.userLMPairing.userId}`,
                    headers: { "Authorization": "Bearer " + authStore.accessToken, }
                } 
        
                try {
                    const response = await axios(config);   
                    return response.data;
                } catch (error) {
                    if (error.response.status == 401) {
                        try {
                            await refreshAccessToken()    
                            const response = await axios({...config, headers: { "Authorization": "Bearer " + authStore.accessToken }});   
                            return response.data;
                        } catch (error) {
                            // refreshAccessToken will report errors
                        }
                    }
        
                    reportAxiosError('Something went wrong when retrieving landmarks', error)
                    throw new Error;
                }      
            }
            else {
                console.log("Can't check landmark rating pairing. Given id values must not be null.")
            }
        }
    }

     /**
     * The callback responsible for adding a new {@link Landmark} to the server, used by the [react-query useMutation]{@link https://react-query.tanstack.com/reference/useMutation#_top} hook.
     * * @memberOf useLandmarks
     */
    const createLandmark = async (data: {landmarkValue: Landmark | undefined, photos?: LMPhoto[]}): Promise<Landmark | undefined> => {
        if (data.landmarkValue) {
            const config: AxiosRequestConfig = {
                method: 'POST',
                data: {
                    landmark: data.landmarkValue,
                    photos: data.photos
                },
                url: API_URL + `/api/landmark/`,
                headers: { "Authorization": "Bearer " + authStore.accessToken, }
            } 
    
            try {
                const response = await axios(config);   
                return response.data;
            } catch (error) {
                if (error.response.status == 401) {
                    try {
                        await refreshAccessToken()    
                        const response = await axios({...config, headers: { "Authorization": "Bearer " + authStore.accessToken }});   
    
                        return response.data;
                    } catch (error) {
                        // refreshAccessToken will report errors
                    }
                }
    
                reportAxiosError('Something went wrong when retrieving landmarks', error)
                throw new Error;
            }   
        }
        else {
            console.log("Can't create landmark. Given landmark value is null.")
        }
    }

    /**
     * The callback responsible for updating a {@link Landmark} on the server, used by the [react-query useMutation]{@link https://react-query.tanstack.com/reference/useMutation#_top} hook.
     * * @memberOf useLandmarks
     */
    const editLandmark =  async (landmarkValue: Landmark) => {
        if (landmarkValue) {
            const config: AxiosRequestConfig = {
                method: 'PUT',
                data: landmarkValue,
                url: API_URL + `/api/landmark/`,
                headers: { "Authorization": "Bearer " + authStore.accessToken, }
            } 

            try {
                const response = await axios(config);   
                return response.data;
            } catch (error) {
                if (error.response.status == 401) {
                    try {
                        await refreshAccessToken()    
                        const response = await axios({...config, headers: { "Authorization": "Bearer " + authStore.accessToken }});   

                        return response.data;
                    } catch (error) {
                        // refreshAccessToken will report errors
                    }
                }

                reportAxiosError('Something went wrong when retrieving landmarks', error)
                throw new Error;
            }
        }
        else {
            console.log("Can't update landmark. Given landmark value is null.")
        }
    }

    /**
     * The callback responsible for updating a {@link Landmark} on the server, used by the [react-query useMutation]{@link https://react-query.tanstack.com/reference/useMutation#_top} hook.
     * * @memberOf useLandmarks
     */
     const rateLandmark =  async (data: {id: string, rating: 1 | -1}) => {
        const config: AxiosRequestConfig = {
            method: 'POST',
            data: data,
            url: API_URL + `/api/landmark/rate/`,
            headers: { "Authorization": "Bearer " + authStore.accessToken, }
        } 

        try {
            const response = await axios(config);   
            return response.data.rating;
        } catch (error) {
            if (error.response.status == 401) {
                try {
                    await refreshAccessToken()    
                    const response = await axios({...config, headers: { "Authorization": "Bearer " + authStore.accessToken }});   

                    return response.data;
                } catch (error) {
                    // refreshAccessToken will report errors
                }
            }

            console.log(error)
            reportAxiosError('Something went wrong when retrieving landmarks', error)
            throw new Error;
        }
    }

    /**
     * The callback responsible for deleting a {@link Landmark} from the server, used by the [react-query useMutation]{@link https://react-query.tanstack.com/reference/useMutation#_top} hook.
     * * @memberOf useLandmarks
     */
    const removeLandmark =  async (id?: string | null) => {
        const config: AxiosRequestConfig = {
            method: 'DELETE',
            url: API_URL + `/api/landmark/${id}`,
            headers: { "Authorization": "Bearer " + authStore.accessToken, }
        } 
        try {
            const response = await axios(config);   
            return response.data;
        } catch (error) {
            if (error.response.status == 401) {
                try {
                    await refreshAccessToken()    

                    // add new access token to header
                    const response = await axios({...config, headers: {"Authorization": "Bearer " + authStore.accessToken}});   

                    return response.data;
                } catch (error) {
                    // refreshAccessToken will report errors
                }
            }

            reportAxiosError('Something went wrong when retrieving landmarks', error)
            throw new Error;
        }
    }

    // get-all query
    const { data: landmarks, status: getLandmarksStatus, refetch: refetchLandmarks } = useQuery<Landmark[], Error>('getLandmarks', () => getLandmarks(), {
        placeholderData: () => queryClient.getQueryData('getLandmarks'),
        staleTime: 1000,
        refetchInterval: 30000,
        refetchOnReconnect: true,
        refetchOnMount: false
    })

    // get-details query
    const { data: landmark, status: getLandmarkStatus, refetch: refetchLandmark } = useQuery<Landmark, Error>(['getLandmark', options?.landmarkId], () => getLandmark(options?.landmarkId), {
        placeholderData: () => queryClient.getQueryData('getLandmark'),
        refetchOnReconnect: true,
        refetchOnMount: false
    })

    const { data: landmarkRatedByUser, status: checkIfRatedByUserStatus, refetch: refetchCheckIfRatedByUser } = useQuery<boolean, Error>(['checkIfRatedByUser', options?.userLMPairing], () => checkIfRatedByUser(options?.userLMPairing), {
        placeholderData: () => queryClient.getQueryData('checkIfRatedByUser'),
        refetchOnReconnect: true,
        refetchOnMount: false
    })

    // mutations
    const { status: addLandmarkStatus, mutateAsync: addLandmarkAsync, reset: resetAddLm, data: newLandmark } = useMutation(createLandmark, {
        onSuccess: data => {
            queryClient.invalidateQueries('getLandmarks')
        },  
    })

    const { status: updateLandmarkStatus, mutateAsync: updateLandmark, reset: resetUpdateLm } = useMutation(editLandmark, {
        onSuccess: () => { queryClient.invalidateQueries('getLandmarks')},  
        onError: () => queryClient.invalidateQueries('getLandmarks'),  
    })

    const { data: rating, status: rateLandmarkStatus, mutateAsync: rateLandmarkAsync, reset: resetRateLandmark } = useMutation(rateLandmark, {
        onSuccess: () => { 
            queryClient.invalidateQueries('getLandmarks')
            queryClient.invalidateQueries('checkIfRatedByUser')
        },  
        onError: () => queryClient.invalidateQueries('getLandmarks'),  
    })

    const { status: deleteLandmarkStatus, mutateAsync: deleteLandmark, reset: resetDeleteLm } = useMutation(removeLandmark, {
        onSuccess: () => queryClient.invalidateQueries('getLandmarks'),  
        onError: () => queryClient.invalidateQueries('getLandmarks'),  
    })

    const { status: addPhotoStatus, mutateAsync: addPhoto, reset: resetAddPhoto } = useMutation(addLandmarkPhoto, {
        onSuccess: () => {
            queryClient.invalidateQueries('getLandmark')
        },
        
        onError: () => {
            queryClient.invalidateQueries('getLandmark')
        },
    })

    const { status: deletePhotoStatus, mutateAsync: deletePhoto, reset: resetDeletePhoto } = useMutation(deleteLandmarkPhoto, {
        onSuccess: () => {
            queryClient.invalidateQueries('getLandmark')
        },
        
        onError: () => {
            queryClient.invalidateQueries('getLandmark')
        },
    })

    return { 
        landmarks, getLandmarksStatus, refetchLandmarks,  //reading
        landmark, getLandmarkStatus, refetchLandmark, //photos
        addPhoto, addPhotoStatus, resetAddPhoto, //add photos
        deletePhoto, deletePhotoStatus, resetDeletePhoto, //delete photos
        addLandmarkAsync, resetAddLm, addLandmarkStatus, newLandmark, // creating
        updateLandmark, resetUpdateLm, updateLandmarkStatus, // updating
        deleteLandmark, resetDeleteLm, deleteLandmarkStatus, // deleting
        rateLandmarkAsync, resetRateLandmark, rateLandmarkStatus, rating, // rating
        landmarkRatedByUser, checkIfRatedByUserStatus, refetchCheckIfRatedByUser // check if landmark is rated by user
    }
}