'use strict';

import {Observable, BehaviorSubject} from 'rxjs';
import * as types from 'blow-types';
import {IQuery, IQueryObject, IQueryWhere} from 'blow-query';

export interface IValidator {
  validate(data: any): Observable<IValidationResult>; 
}

export interface IValidationResult {
  isValid: boolean;
  errors: any;
  getInvalidProperties(): any;
  getPropertyErrors(propertyName: string): any;
  toJSON(): any;
}

export interface IManager {
  attachModelToConnection(model: string | IBaseModelConstructor, connection: string | IConnection);
  hasConnection(connectionName: string): boolean;
  getConnection(connectionName: string): IConnection;
  defineConnection(options: IConnectionOptions): IConnection;  
  hasModel(modelName: string): boolean;
  getModel(modelName: string): IBaseModelConstructor;
  defineModel(model: IBaseModelConstructor, options: IModelMetadataOptions, properties?: {[key: string]: IModelPropertyMetadataOptions}, relations?: {[key: string]: IModelRelationMetadataOptions}): void;
  init(): Observable<IManager>;
}

export interface IConnectionOptions extends IAdapterOptions {
  name: string;
  adapter: IAdapterConstructor;
  params?: any
}

export interface IConnection {
  name: string;
  adapter: IAdapter;
  status: BehaviorSubject<string>;
  isConnected: boolean;
  init(): Observable<IConnection>;
  attach(model: IBaseModelConstructor): void;
}

export interface IModelRelationMetadataOptions {
  name: string;
  type: string;
  model: IPersistedModelConstructor;
  foreignKey: string;
  hidden: boolean;
}

export interface IModelRelationMetadata {
  name: string;
  type: string;
  model: IPersistedModelConstructor | string;
  foreignKey: string;
  hidden: boolean;
  apply(model: IPersistedModelConstructor): void;
}

export interface IModelPropertyMetadata {
  name: string;
  columnName: string;
  type: types.Type;
  default: () => any;
  id: boolean;
  index: boolean;
  hidden: boolean;
  validations: IterableIterator<any>;
  apply(model: IBaseModelConstructor): void;
}

export interface IModelPropertyMetadataOptions {
  name: string;
  type: any;
  default?: any;
  id?: boolean;
  columnName?: string;
  index?: boolean;
  hidden?: boolean;
  validations?: {
    [key: string]: any
  };
}

export interface IModelMetadataOptions {
  name: string;
  connection?: string;
  pluralName?: string;
  autoId?: boolean;
}

export interface IModelMetadata {
  name: string;  
  pluralName: string;
  autoId: boolean  
  properties: IterableIterator<IModelPropertyMetadata>;
  relations: IterableIterator<IModelRelationMetadata>;
  connectionName: string;
  idProperty: IModelPropertyMetadata;
  validationSchema: {[key: string]: any};
  asyncValidationSchema: {[key: string]: any};
  defineProperty(options: IModelPropertyMetadataOptions): void;
  buildPropertyId(name: string, type: any): void;
  getProperty(name: string): IModelPropertyMetadata;
  hasProperty(name: string): boolean;
  isAllowedProperty(name: string): boolean;
  defineRelation(options: IModelRelationMetadataOptions): void;
  apply(model): void;
}

export interface IBaseModelData {
  [field: string]: any;
}

export interface IBaseModelConstructor {
  new(data?: IBaseModelData): IBaseModel;
  metadata: IModelMetadata;
  ready: boolean;
  prototype: Object;
  validator: IValidator;
} 

export interface IBaseModel {
  merge(data: IBaseModelData): IBaseModel;  
  toJSON(withHidden?: boolean): IBaseModelData;
  toSafeJSON(): IBaseModelData;  
  inspect(): IBaseModelData;
}

export interface IPersistedModelConstructor extends IBaseModelConstructor {
  new(data?: IBaseModelData): IPersistedModel;
  count(where?: IQueryWhere): Observable<number>;
  create(data: IBaseModelData | IPersistedModel): Observable<IPersistedModel>;
  destroy(where?: IQueryWhere): Observable<number>;
  destroyById(id: any): Observable<boolean>;
  exists(query: IQuery | IQueryObject): Observable<boolean>;
  find(query?: IQuery | IQueryObject): Observable<IPersistedModel>;
  findOne(query?: IQuery | IQueryObject): Observable<IPersistedModel>;
  findById(id: any): Observable<IPersistedModel>;
  findOrCreate(where: IQueryWhere, data: IBaseModelData): Observable<IPersistedModel>;
  update(where: IQueryWhere, data: IBaseModelData): Observable<number>;
  updateOrCreate(data: IBaseModelData | IPersistedModel): Observable<IPersistedModel>;
}

export interface IPersistedModel extends IBaseModel {}

export interface IPersistedAdapterConstructor extends IAdapterConstructor {}

export interface IPersistedAdapter extends IAdapter {
  count(metadata: IModelMetadata, where?: IQueryWhere): Observable<number>;
  create(metadata: IModelMetadata, data: any): Observable<any>;  
  destroy(metadata: IModelMetadata, where?: IQueryWhere): Observable<number>;  
  destroyById(metadata: IModelMetadata, id: any): Observable<boolean>;  
  exists(metadata: IModelMetadata, id: any | IQueryObject): Observable<boolean>;  
  find(metadata: IModelMetadata, query?: IQuery | IQueryObject): Observable<any>;  
  findOne(metadata: IModelMetadata, query?: IQuery | IQueryObject): Observable<any>;  
  findById(metadata: IModelMetadata, id: any): Observable<any>;  
  findOrCreate(metadata: IModelMetadata, where: IQueryWhere, data: any): Observable<any>;  
  update(metadata: IModelMetadata, where: IQueryWhere, data: any): Observable<number>;  
  updateOrCreate(metadata: IModelMetadata, data: any): Observable<any>;
}

export interface IAdapterOptions {
  [key: string]: any;
}

export interface IAdapterConstructor {
  new (options: IAdapterOptions): IAdapter;
  init(options: IAdapterOptions): Observable<IAdapter>;
}

export interface IAdapter {
  idPropertyName: string;
  idPropertyType(): any;
  connect(): Observable<IAdapter>;
  toJSON(): {[key: string]: string}; 
  inspect(): {[key: string]: string};
}

export interface IHasManyRelation<T> {
  create(data: {[key: string]: any} | T): Observable<T>;
  destroy(where?: IQueryWhere): Observable<number>;
  destroyById(id: any): Observable<boolean>;
  find(query?: IQuery | IQueryObject): Observable<T>;
  findById(id: any): Observable<T>;
}

export interface IBelongsToRelation<T> {
  (data: {[key: string]: any} | T): Observable<T> | void;  
}