/**
 * Servicio de comunicación API
 * 
 * @author Moisés Cortés C. <moises.cortes@notsoweb.com>
 * @version 1.0.0
 */

import axios from 'axios';
import { reactive, ref } from 'vue';

axios.defaults.withXSRFToken = true;
axios.defaults.withCredentials = true;

/**
 * Códigos de falla
 */
const failCodes = [
    400,
    409,
    422
];

/**
 * Servidor a utilizar
 */
const token     = ref(localStorage.token);
const csrfToken = ref(localStorage.csrfToken);

/**
 * Define el token de la api
 */
const defineApiToken = (x) => {
    token.value = x;
    localStorage.token = x;
}

/**
 * Define CSRF token
 */
const defineCsrfToken = (x) => {
    csrfToken.value = x;
    localStorage.csrfToken = x;
}

/**
 * Define el token de la api
 */
const resetApiToken = () => {
    token.value = undefined;
    localStorage.removeItem('token');
}

/**
 * Reset CSRF token
 */
const resetCsrfToken = () => {
    csrfToken.value = undefined;
    localStorage.removeItem('csrfToken');
}

/**
 * Determina si el token tiene algo o no
 */
const hasToken = () => {
    return token.value !== undefined;
}

/**
 * Fuerza el cierre de la sesión
 */
const closeSession = () => {
    resetApiToken()
    resetCsrfToken()

    Notify.info(Lang('session.closed'))

    location.replace('/')
}

/**
 * Composición de llaves
 * 
 * Utilizado para transformar llaves en FormData
 */
function composeKey(parent, key) {
  return parent ? parent + '[' + key + ']' : key
}

/**
 * URL API
 */
const apiURL = (path) => {
    return import.meta.env.VITE_API_URL + '/api/' + path
}

/**
 * Instancia de la API de uso directo
 */
const api = {
    errors: {},
    hasErrors: false,
    processing: false,
    wasSuccessful: false,
    async load({
        method,
        url,
        apiToken = token.value,
        options = { 
            data:{},
            params:{}
        }
    }) {
        this.errors        = {};
        this.hasErrors     = false;
        this.processing    = true;
        this.wasSuccessful = false;

        try {
            if(options.hasOwnProperty('onStart')) {
                options.onStart();
            }

            let { data } = await axios({
                method: method,
                url,
                data: options.data,
                params: options.params,
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json',
                    'Authorization': `Bearer ${apiToken}`
                }
            });

            if(data.status == 'success') {
                this.wasSuccessful = true;

                if(options.hasOwnProperty('onSuccess')) {
                    options.onSuccess(data.data, data);
                }
            } else if(data.status == 'fail') {
                if(options.hasOwnProperty('onFail')) {
                    options.onFail(data.data);
                }
            }

            if(options.hasOwnProperty('onFinish')) {
                options.onFinish(data.data);
            }
        } catch (error) {
            console.error(error)

            this.hasErrors = true;

            let { response } = error

            // Código de sesión invalida
            if(response.status === 401 && response.data?.message == 'Unauthenticated.') {
                Notify.error(Lang('session.expired'));

                closeSession();

                return
            }

            // Fallas
            if(failCodes.includes(response.status)) {
                options.hasOwnProperty('onFail')
                    ? options.onFail(response.data.data)
                    : Notify.warning(response.data.data.message);

                return
            }

            if(options.hasOwnProperty('onError')) {
                options.onError(response.data);
            }

            if(response.data != null) {
                this.errors = response.data.errors;
            }
        }

        this.processing = false;
    },
    get(url, options) {
        this.load({
            method: 'get',
            url,
            options
        })
    },
    post(url, options) {
        this.load({
            method: 'post',
            url,
            options
        })
    },
    put(url, options) {
        this.load({
            method: 'put',
            url,
            options
        })
    },
    patch(url, options) {
        this.load('patch', {
            method: 'patch',
            url,
            options
        })
    },
    delete(url, options) {
        this.load({
            method: 'delete',
            url,
            options
        })
    },
    catalog(resources, options) {
        this.post(apiURL('catalogs/get'), {
            ...options,
            data: resources
        })
    },
    download(url, file, params = {}) {
        axios({
            url: url,
            params: params,
            method: 'GET',
            responseType: 'blob',
            headers: {
                'Authorization': `Bearer ${token.value}`
            }
        }).then((res) => {
            const href = URL.createObjectURL(res.data);
            const link = document.createElement('a');

            link.href = href;
            link.setAttribute('download', file);
            document.body.appendChild(link);
            link.click();
        
            document.body.removeChild(link);

            URL.revokeObjectURL(href);
        });
    },
}

/**
 * Instancia de la API
 */
const useApi = () => reactive({...api});

/**
 * Instancia de la API para formularios
 */
const useForm = (form = {}) =>  {
    // Permite agregar datos mediante una transformación
    let transform = (data) => data

    // Contador de archivos
    let filesCounter = 0

    // Procesar elementos del formulario
    const append = (formData, key, value) => {
        if(Array.isArray(value)) {
            return Array.from(value.keys()).forEach((index) => append(formData, composeKey(key, index), value[index]));
        } else if(value instanceof Date) {
            return formData.append(key, value.toISOString())
        } else if(value instanceof File) {
            filesCounter++
            return formData.append(key, value, value.name)
        } else if (value instanceof Blob) {
            return formData.append(key, value)
        } else if(typeof value === 'boolean') {
            return formData.append(key, value ? '1' : '0')
        } else if (typeof value === 'string') {
            return formData.append(key, value)
        } else if (typeof value === 'number') {
            return formData.append(key, `${value}`)
        } else if(value === null || value === undefined) {
            return formData.append(key, '')
        } else if (typeof value === 'object') {
            objectToFormData(formData, key, value);
        }
    }

     // Convertir objeto a elemento de FormData
    const objectToFormData = (formData, parentKey = null, value) => {
        value = value || {}
        for (const key in value) {
          if (Object.prototype.hasOwnProperty.call(value, key)) {
            append(formData, composeKey(parentKey, key), value[key])
          }
        }

        return formData
    }

    // Transforma todos los datos
    const prepareData = (data) => {
        let formData = new FormData();

        for (let i in data) {
            append(formData, i, data[i]);
        }

        return formData;
    }

    return reactive({
        ...form,
        errors: {},
        hasErrors: false,
        processing: false,
        wasSuccessful: false,
        _inputs: Object.keys(form),
        _original: {
            ...form
        },
        reset() {
            for(let i in this._original) {
                this[i] = this._original[i]
            }
        },
        data() {
            let data = {};

            for (let i in this) {
                if(typeof this[i] !== 'function' && this._inputs.includes(i)){
                    data[i] = this[i]
                }
            }

            return data;
        },
        transform(callback) {
            transform = callback

            return this
        },
        async load({
            method,
            url,
            apiToken = token.value,
            options = { 
                data:{},
                params:{}
            }
        }) {
            this.errors        = {};
            this.hasErrors     = false;
            this.processing    = true;
            this.wasSuccessful = false;

            try {
                if(options.hasOwnProperty('onStart')) {
                    options.onStart(options);
                }

                let { data } = await axios({
                    method: method,
                    url,
                    data: prepareData(transform(this.data())),
                    headers: {
                        'Content-Type': (filesCounter > 0)
                            ? 'multipart/form-data  boundary='
                            : 'application/json',
                        'Accept': 'application/json',
                        'Authorization': `Bearer ${apiToken}`,
                        'X-CSRF-TOKEN': csrfToken.value
                    }
                });

                if(data.status == 'success') {
                    this.wasSuccessful = true;

                    if(options.hasOwnProperty('onSuccess')) {
                        options.onSuccess(data?.data);
                    }
                } else if(data.status == 'fail') {
                    if(options.hasOwnProperty('onFail')) {
                        options.onFail(data?.data);
                    }
                }

                if(options.hasOwnProperty('onFinish')) {
                    options.onFinish(data?.data);
                }
            } catch (error) {
                console.error(error);

                this.hasErrors = true;

                let { response } = error
                
                if(options.hasOwnProperty('onError')) {
                    options.onError(response);
                }

                if(response.data?.errors != null) {
                    this.errors = response.data.errors;

                    for(let e in this.errors) {
                        Notify.error(this.errors[e])
                    }
                }
            }

            this.processing = false;
        },
        fill(model) {
            this._inputs.forEach(element => {
                this[element] = (element == 'is_active')
                    ? (model[element] == 1)
                    : model[element] ?? this[element]
            });
        },
        get(url, options) {
            this.load({
                method: 'get',
                url,
                options
            })
        },
        post(url, options) {
            this.load({
                method: 'post',
                url,
                options
            })
        },
        put(url, options) {
            this.load({
                method: 'put',
                url,
                options
            })
        },
        patch(url, options) {
            this.load('patch', {
                method: 'patch',
                url,
                options
            })
        },
        delete(url, options) {
            this.load({
                method: 'delete',
                url,
                options
            })
        },
    })
}

/**
 * Instancia de a API para buscador
 */
const useSearcher = (options = {
    url: '',
    filters: ''
}) => reactive({
    query: '',
    errors: {},
    hasErrors: false,
    processing: false,
    wasSuccessful: false,
    async load({
        url,
        apiToken = token.value,
        filters,
    }) {
        this.errors = {};
        this.processing = true;
        this.hasErrors = false;
        this.wasSuccessful = false;

        try {
            if(options.hasOwnProperty('onStart')) {
                options.onStart();
            }

            let { data } = await axios({
                method: 'get',
                url,
                params: {
                    q: this.query,
                    ...filters
                },
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json',
                    'Authorization': `Bearer ${apiToken}`
                }
            });

            if(data.status == 'success') {
                this.wasSuccessful = true;

                if(options.hasOwnProperty('onSuccess')) {
                    options.onSuccess(data.data);
                }
            } else if(data.status == 'fail') {
                if(options.hasOwnProperty('onFail')) {
                    options.onFail(data.data);
                }
            }

            if(options.hasOwnProperty('onFinish')) {
                options.onFinish(data.data);
            }
        } catch (error) {
            console.error(error);

            this.hasErrors = true;

            let { response } = error

            // Código de sesión invalida
            if(response.status === 401 && response.data.message == 'Unauthenticated.') {
                Notify.error(Lang('session.expired'));
                closeSession();
                return
            }
            
            if(options.hasOwnProperty('onError')) {
                options.onError(response);
            }

            if(response.data?.errors != null) {
                this.errors = response.data.errors;
                for(let e in this.errors) {
                    Notify.error(this.errors[e])
                }
            }
        }

        this.processing = false;
    },
    pagination(url, filter = {}) {
        this.load({
            url,
            filters : {
                ...options.filters,
                ...filter,
            }
        })
    },
    search(q = '', filter = {}) {
        this.query = q
        this.load({
            url: options.url,
            filters : {
                ...options.filters,
                ...filter,
            }
        })
    },
    refresh(filter = {}) {
        this.load({
            url: options.url,
            filters : {
                ...options.filters,
                ...filter,
            }
        })
    }
})

export {
    api,
    token,
    apiURL,
    closeSession,
    defineCsrfToken,
    defineApiToken,
    hasToken,
    resetApiToken,
    useApi,
    useForm,
    useSearcher
}