import axios from 'axios';
import { encode as btoa } from 'base-64';
import { format } from 'date-fns';
import React, { createContext, useContext, useEffect, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { SettingsContext } from './SettingsProvider';
import { ThemeButton, ThemeText } from './common/ThemeTypes';
import { useColors } from './common/colors';
import Login from './screens/Login';
import { ToastType, useToast } from './common/ToastProvider';

export const InterfaceContext = createContext<any>(null);

const Interface = ({ children }) => {
    const colors = useColors();
    const { showToast, hideToast } = useToast();
    const { settings } = useContext(SettingsContext);
    const [userInformation, setUserInformation] = useState(null);
    const [baseData, setBaseData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [initialLoading, setInitialLoading] = useState(true);
    const [error, setError] = useState(false);
    const [errorObject, setErrorObject] = useState(null);

    const fetchData = async () => {
        try {
            setLoading(true)
            if (settings.requireAuth ? !settings.username : false) throw new InterfaceError("Authentication Required")
            if (!settings.url) throw new InterfaceError("Invalid URL")
            const userinformation = await fetchUserInformation(settings.username);
            const basedata = await fetchBaseData();
            setUserInformation(userinformation)
            setBaseData(basedata)
            if (!userinformation || !basedata) throw new InterfaceError("Data returned is Null");
            setError(false)
            setInitialLoading(false)
        } catch (error) {
            console.error(error);
            setErrorObject(error);
            setError(true)
        } finally {
            setLoading(false)
        }
    }

    useEffect(() => {
        fetchData();
    }, [settings]);

    const refresh = () => {
        fetchData();
    };

    const axiosInstance = axios.create({
        baseURL: settings.url,
    });

    axiosInstance.interceptors.request.use(
        (config) => {
            if (!settings.url) {
                throw new InterfaceError("Invalid URL")
            }
            if (settings.requireAuth) {
                const { username, password } = settings;
                const token = btoa(`${username}:${password}`);
                config.headers.Authorization = `Basic ${token}`;
            }

            return config;
        },
        (error) => Promise.reject(error)
    );

    const fetchBaseData = async () => {
        return await requestGet('basedata/');
    };

    const fetchUserInformation = async (username) => {
        try {
            return await requestGet(`userinformation/?username=${username}`, false); 
        } catch (error) {
            if (error.response.status === 404) {
                const demoinformation = await requestGet(`demoinformation/`);
                if (demoinformation) {   
                  console.log("Using Demoinformation");
                  showToast(ToastType.Info, "You are using a DEMOUSER", 5000);
                }
                return demoinformation;
            }
            console.error("Error fetching user information:", error);
            return Null;
        }
    };

    const fetchTimes = async () => {
        return await requestGet('hours/');
    };

    const fetchScheduleByWeek = async (type, value, group, week, year) => {
        const groupparam = type === 'class' && group ? `&group=${group}` : '';
        return await requestGet(`schedule/week/?type=${type}&value=${value}${groupparam}&week=${week}&year=${year}`);
    };

    const fetchScheduleByDate = async (type, value, group, date) => {
        const groupparam = type === 'class' ? `&group=${group}` : '';
        return await requestGet(`schedule/date/?type=${type}&value=${value}${groupparam}&date=${format(date, 'yyyy-MM-dd')}`);
    };

    const fetchAppointments = async (date) => {
        return await requestGet(`appointments/?date=${format(date, 'yyyy-MM-dd')}`);
    };

    const fetchTests = async (type, value, date) => {
        return await requestGet(`tests/?type=${type}&value=${value}&date=${format(date, 'yyyy-MM-dd')}`);
    };

    const fetchDayTypes = async (from, until) => {
        return await requestGet(`daytypes/?from=${format(from, 'yyyy-MM-dd')}&until=${format(until, 'yyyy-MM-dd')}`);
    };

    const fetchFreeRooms = async (date, from, until) => {
        return await requestGet(`freerooms/?date=${format(date, 'yyyy-MM-dd')}&from=${from}&until=${until}`);
    };


    const requestGet = async (url, handleError = true) => {
        console.log(url)
        try {
            const response = await axiosInstance.get(url);
            if (response.status === 200 && response.data.success === true) {
                return response.data.data;
            } else {
                throw new NetworkError(response.data.message, url);
            }
        } catch (error) {
            if (!handleError) {
                throw(error);
            } else if (error.response) {
                if (error.response.data.message) {
                    throw new NetworkError(error.response.status + ": " + error.response.data.message, url);
                } else if (error.response.status === 404) {
                    throw new NetworkError('404: URL wurde auf dem Server nicht gefunden', url);
                } else if (error.response.status === 502) {
                    throw new NetworkError('502: Webservice ist unerreichbar', url);
                }
                throw new NetworkError(`${error.response.status}: Ein unbekannter ist auf dem Server aufgetreten`, url);
            } else if (error.request) {
                throw new NetworkError('Netzwerkfehler', url);
            } else {
                throw new NetworkError(error.message || 'Ein unbekannter fehler ist aufgetreten', url);
            }
        }
    };



    const styles = StyleSheet.create({
        container: {
            paddingTop: 30,
            flex: 1,
        },
        titleBox: {
            paddingHorizontal: 20,
            paddingVertical: 10,
            backgroundColor: colors.backSecondary,
        },
        title: {
            fontSize: 20,
            fontWeight: 'bold',
        },
        button: {
            marginTop: 5,
        }
    })

    const renderContent = () => {
        let needsCredentials = settings.requireAuth ? !settings.username : false;
        if (error && !loading) return (
            <View style={styles.container}>
                {!needsCredentials &&
                    <View style={styles.titleBox}>
                        <ThemeText style={styles.title}>Fehler beim laden der Daten</ThemeText>
                        <ThemeText>{errorObject?.message}</ThemeText>
                        <ThemeButton style={styles.button} onPress={refresh}><Text>Neu Versuchen</Text></ThemeButton>
                    </View>
                }
                <Login />
            </View>
        )
        if (!initialLoading) return (children)
    }

    return (
        <InterfaceContext.Provider value={{ userInformation, baseData, refreshInterface: refresh, fetchBaseData, fetchTimes, fetchScheduleByWeek, fetchScheduleByDate, fetchAppointments, fetchTests, fetchDayTypes, fetchFreeRooms }}>
            {renderContent()}
        </InterfaceContext.Provider>
    );
};

export default Interface;

export class InterfaceError extends Error {
    constructor(message) {
        super(message);
        this.name = "InterfaceError";
    }
}
export class NetworkError extends Error {
    url: string;

    constructor(message: string, url: string) {
        super(message);
        this.name = "NetworkError";
        this.url = url;
    }
}
