import { Api } from '../../api';
import { Util } from '../../util/util';
declare var Promise, fetch, jDrupal;

export class JsonApi extends Api {

    public entities = {};
    public collections = {};
    public nodes = {};
    public resources: any = {};
    public views = {};
    public cache = false;
    public language: string;

    constructor() {
        super();
        this.headers = {
            'Accept': 'application/vnd.api+json',
            'Content-Type': 'application/vnd.api+json'
        };
    }

    public async post(path, object: any) {
        this.headers['Authorization'] = 'Basic '+jDrupal.settings.auth_hash;
        this.headers['X-CSRF-Token'] = jDrupal.settings.user_csrf_token;
        this.headers['Content-Type'] = 'application/vnd.api+json';
        await fetch(`${this.baseUrl}${path}`, {
            headers: this.headers,
            method: 'POST',
            body: JSON.stringify( object )
        });
    }

    public async patch(path, id, object: any) {
        this.headers['Authorization'] = 'Basic '+jDrupal.settings.auth_hash;
        this.headers['X-CSRF-Token'] = jDrupal.settings.user_csrf_token;
        this.headers['Content-Type'] = 'application/vnd.api+json';
        if (object.data.attributes.changed ) {
            object.data.attributes.changed = undefined;
        }
        await fetch(`${this.baseUrl}${path}/` + id, {
            headers: this.headers,
            method: 'PATCH',
            body: JSON.stringify( object )
        });
    }

    // FIXME: causes php server error on drupal content admin page
    public async delete(path, id) {
        this.headers['Authorization'] = 'Basic ' + jDrupal.settings.auth_hash;
        this.headers['X-CSRF-Token'] = jDrupal.settings.user_csrf_token;
        this.headers['Content-Type'] = 'application/vnd.api+json';
        // await fetch(`${this.baseUrl}${path}/` + id, {
        //     headers: this.headers,
        //     method: 'DELETE'
        // });
    }

    public async getResourcesIndex(jsonapi_endpoint: string = 'jsonapi', language: string = undefined) {
        const self = this;
        await new Promise((resolve, reject) => {
            let url = self.baseUrl + 'index.php/';
            if (language) {
                url += language + '/';
            }
            url += jsonapi_endpoint;
            fetch(url)
                .then((response) => {
                    if (response.status >= 400) {
                        throw new Error('JsonApi: Bad resources response from server');
                    }
                    return response.json();
                })
                .then((result) => {
                    self.resources = result;
                    self.baseUrl = result.links.self + '/';
                    Object.keys(result.links).forEach((k, i) => {
                        if (   k.match('node--')
                            || k.match('menu--')
                            || k.match('comment--')
                            || k.match('file--')
                            || k.match('taxonomy_term--')
                            || k.match('node_type--')
                            // || k.match('view--')
                            )
                        {
                            self.collections[k] = {};
                        }
                    });
                    resolve(result);
                });
        });
    };

    public async getCollection(collectionId: string, params: any = undefined, getRelationships: boolean = false) {
        let path = collectionId.split('--')[0] + '/' + collectionId.split('--')[1];
        let collection =  this.collections[collectionId] = await this.get(path,{});

        if(getRelationships) {
            let relatedPromises = [];
            Object.keys(collection.data).forEach((k) => {
                relatedPromises.push(this.getEntityRelationships({data:collection.data[k]}));
            });
            for (const k in relatedPromises) {
                await relatedPromises[k];
            }
        }
        Object.keys(collection.data).forEach((k, i) => {
           this.entities[collection.data[k].attributes.uuid] = collection.data[k];
        });
        return collection;
    }


    public async getCollections(collectionIds: any[], params: any = undefined, getRelationships: boolean = false) {
        let promises = [];
        collectionIds.forEach((id) => {
            promises.push(this.getCollection(id,params,getRelationships))
        });
        for (let k in promises) {
            await promises[k];
        }
    }

    public async getAllCollections(getRelationships: boolean = false, exclude: any[] = []) {
        let promises = [];
        Object.keys(this.collections).forEach((k, i) => {
            let excluded = false;
            exclude.forEach((id) => {
                if (id === k) {
                    excluded = true
                }
            });
            if (!excluded){
                promises.push(this.getCollection(k,{},getRelationships));
            }
        });
        for(const k in promises) {
            await promises[k];
        }
    }

    public async getResource(url: string, params: any = undefined) {
        if (params) {
            url += '?';
            Object.keys(params).forEach(key => url += key + '=' + params[key] + '&');
        }
        let resource: any = await fetch(url, { headers: this.headers} )
            .then((response) => {
                if (response.status >= 400) {
                    throw new Error('Bad response from server');
                }
                return response.json();
            })
            .then((data) => {
                // set entity + get relationships
                if (data.data && data.data.id) {
                    this.entities[data.data.id] = data;
                }
                // set entities array + get relationships
                if (data.data && Array.isArray(data.data)) {
                    data.data.forEach((item: any) => {
                        if(item.id) {
                            this.entities[item.id] = item;
                        }
                    });
                }
                return data;
            });
        return resource;
    }

    public async getResources(urls: any[]) {
        let promises = [];
        urls.forEach((url) => {
            promises.push(this.getResource(url));
        });
        for (let k in promises) {
            await promises[k];
        }
    }

    // todo:
    public async getEntityRelationships(entity: any) {
        // console.log(entity);
        return await new Promise(resolve => {
            let relationships: any[] = [];
            if(entity.data && entity.data.relationships) {
                Object.keys(entity.data.relationships).forEach((k, i) => {
                    // todo: nodes only: check other entity relation types
                    if (k.match('field_')){
                        relationships.push(entity.data.relationships[k])
                    }
                });
            }
            if (relationships.length) {
                let urls: any[] = []
                relationships.forEach((relationship: any) => {
                    if (relationship.links && relationship.links.self) {
                        urls.push(relationship.links.related)
                    }
                });
                if (urls.length) {
                    this.getResources(urls).then(() => {
                        // console.log(urls);
                        resolve();
                    });
                }else{
                    resolve();
                }
            } else {
                resolve();
            }
        });
    }

    public async getNode(id: any, type: string = undefined) {
        const self = this;
        id = Util.castToType(id);
        let idField = 'uuid';
        if (typeof id === 'number') {
            idField = 'nid';
        }
        return await new Promise(resolve => {
            let path: string;

            // cached node
            if (this.cache){
                let cachedNode: any;
                Object.keys(this.entities).forEach((k) => {
                    if(this.entities[k].data && this.entities[k].data.attributes[idField] == id) {
                        cachedNode = this.entities[k];
                    }
                });
                if(cachedNode) {
                    resolve(cachedNode);
                    return;
                }
            }

            // define type from node_index view
            if(this.views['node_index']) {
                this.views['node_index'].forEach((item) => {
                    if(item[idField] == id) {
                        type = item.type;
                    }
                });
            }

            if (type) {
                path = 'node/' + type;
                this.get(path + '?filter[' + idField + '][value]=' + id).then((res) => {
                    parseNodeResult(res);
                });
            } else {
                // search through all node collections
                let nodeTypes = [];
                Object.keys(this.collections).forEach((k) => {
                    if(k.match('node--')) {
                        nodeTypes.push(k);
                    }
                });
                nodeTypes.forEach((type) => {
                    let path = type.split('--')[0] + '/' + type.split('--')[1];
                    // fixme: stop further http requests after node match response
                    this.get(path + '?filter['+idField+'][value]=' + id).then((res) => {
                        parseNodeResult(res);
                    });
                });
            }

            function parseNodeResult(res) {
                if(res.data.length) {
                    let node = {data: res.data[0]};
                    self.entities[res.data[0].id] = node;
                    if(!self.collections[res.data[0].type].data){
                        self.collections[res.data[0].type].data = [];
                    }

                    // fixme: todo: check for doubles before push
                    self.collections[res.data[0].type].data.push(node);
                    // todo: deprecate, use collections + entities only
                    self.nodes[res.data[0].attributes.nid] = node;

                    // get relationships
                    if (node.data.relationships) {
                        self.getEntityRelationships(node).then(() => {
                            resolve(node);
                        });
                    }
                }
            }
        });
    }

    public async getNodes(ids: any[]) {
        let promises = [];
        ids.forEach((id) => {
            promises.push(this.getNode(id));
        });
        for (let k in promises) {
            await promises[k];
        }
    }

    public async saveNode(node: any) {
        let path = node.data.type.split('--')[0] + '/' + node.data.type.split('--')[1];
        if (!node.data.id) {
            await this.post(path, node );
        } else {
            await this.patch(path, node.data.id , node);
        }
    }

    public async deleteNode(node: any) {
        let path = node.data.type.split('--')[0] + '/' + node.data.type.split('--')[1];
        await this.delete(path, node.data.id).then((arg) => {
            // fixme: todo: delete from collections + entities
            // if (this.entities[node.data.id]){
            //     this.entities[node.data.id] = undefined;
            // }
            // let collection = this.collections[node.data.type];
            // if(collection.data && collection.data.length) {
            //     collection.data.forEach((collectionNode,i) => {
            //         if(node.data.id === collectionNode.data.id){
            //             // collection.data.slice(i)
            //         }
            //     });
            // }
        });
    }





    // todo: check
    getById(entity_id: string) {
        let entity: any;
        Object.keys(this.collections).forEach((k, i) => {
            if(this.collections[k]['data'] && this.collections[k]['data'].length){
                this.collections[k]['data'].forEach((entityObject) => {
                    if (entityObject.id === entity_id) {
                        entity = entityObject;
                    }
                });
            }
        });
        if (entity) {
            return { data: entity };
        }
        // return entity;
    }

    public async getByNodeId(nid: any) {
        let entity: any;
        Object.keys(this.collections).forEach((k, i) => {
            if(k.match('node--') && this.collections[k]['data'] && this.collections[k]['data'].length){
                this.collections[k]['data'].forEach((entityObject) => {
                    if (entityObject.attributes.nid == nid) {
                        entity = entityObject;
                    }
                });
            }
        });
        if (entity) {
            return {data: entity};
        } else {

        }
    }



    // public async entityLoad(entity_type_id: string, bundle_id: string, entity_id: any) {
    //     if(entity_type_id === 'node'){
    //         // if( typeof entity_id === 'number'){  }
    //     }
    //     // return await this.get(entity_type_id+'/'+bundle_id+'/'+entity_id);
    //     console.log(entity_type_id+'/'+bundle_id+'/'+entity_id);
    // }





    getByNid(nid, type: string = undefined) {

    }



}
