'use strict';

import {isUndefined, isFunction, isNull} from 'util';
import {Observable} from 'rxjs';
import {IQueryWhere, IQuery, Query, IQueryObject} from 'blow-query';
import * as _ from './helpers';
import {manager} from './manager';
import {
  IModelRelationMetadataOptions, 
  IModelRelationMetadata,
  IPersistedModelConstructor
} from './interfaces';


export class ModelRelationMetadata implements IModelRelationMetadata {

  protected _name: string;
  protected _type: string;
  protected _model: IPersistedModelConstructor | string;
  protected _foreignKey: string;
  protected _hidden: boolean;

  constructor(options: IModelRelationMetadataOptions) {
    this._name = options.name;
    this._type = options.type;
    this._model = options.model;
    this._foreignKey = options.foreignKey;
    this._hidden = isUndefined(options.hidden) ? false : options.hidden;
  }

  get name(): string {
    return this._name;
  }
  
  get type(): string {
    return this._type;
  }
  
  get model(): IPersistedModelConstructor | string {
    return this._model;
  }
  
  get foreignKey(): string {
    return this._foreignKey;
  }
  
  get hidden(): boolean {
    return this._hidden;
  }
  
  apply(model: IPersistedModelConstructor): void {    
    
    switch(this.type) {
      case 'belongsTo': {
        const relationName = this.name;
        const targetModel = <IPersistedModelConstructor>(_.getModel(this.model));
        const targetKey = _.getModel(this.model).metadata.idProperty;
        
        if(!model.metadata.hasProperty(this.foreignKey)) {
          model.metadata.defineProperty({
            name: this.foreignKey,
            type: targetKey.type
          }); 
        }
        const rootModel = model;
        const rootKey = model.metadata.getProperty(this.foreignKey);
       
        Object.defineProperty(model.prototype, relationName, {
          enumerable: false,
          configurable: false,
          get: function() {
            const inst = this;
            return function(data) {
              if(arguments.length > 0) {
                inst[rootKey.name] = data[targetKey.name]; 
              }
              else {
                if(inst[rootKey.name]) {
                  return targetModel.findById(inst[rootKey.name]);
                } else {
                  return Observable.of(undefined);
                }                 
              }
            }
          }
        });
    
      } break;
      case 'hasMany': {
        const relationName = this.name;
        const rootModel = model;
        const rootKey = model.metadata.idProperty;
        
        const targetMetadata = _.getModel(this.model).metadata;                
        if(!targetMetadata.hasProperty(this.foreignKey)) {
          targetMetadata.defineProperty({
            name: this.foreignKey,
            type: rootKey.type
          }); 
        }
        const targetModel = <IPersistedModelConstructor>(_.getModel(this.model));
        const targetKey = targetMetadata.getProperty(this.foreignKey);
        
        Object.defineProperty(model.prototype, relationName, {
          enumerable: false,
          configurable: false,
          get: function() {
            const inst = this;
            
            return {
              create(data: {[key: string]: any}) {
                data[targetKey.name] = inst[rootKey.name];
                return targetModel.create(data);
              },
              
              find(query?: IQuery | IQueryObject) {
                const q: IQuery = new Query(query);
                q.equal(targetKey.name, inst[rootKey.name]);
                return targetModel.find(q);
              },
              
              findById(id: any) {
                const q: IQuery = new Query();
                q.equal(targetKey.name, inst[rootKey.name]);
                q.equal(targetModel.metadata.idProperty.name, id);
                return targetModel.findOne(q);
              },
              
              destroy(query?: IQueryWhere) {
                const q: IQuery = new Query({where: query});
                q.equal(targetKey.name, inst[rootKey.name]);
                return targetModel.destroy(q.toJSON().where);
              },
              
              destroyById(id: any) {
                const q: IQuery = new Query();
                q.equal(targetModel.metadata.idProperty.name, id);
                return this.destroy(q.toJSON().where);
              }
            }
          }
        });
      } break;
    }    
  }
}
