'use strict';
/*jshint expr: true*/
const path = require('path');
const events = require('events');
const chai = require('chai');
const sinon = require('sinon');
const fs = require('fs-extra');
const expect = require('chai').expect;
const periodic = require('../../../index');
const periodicClass = require('../../../lib/periodicClass');
const express = require('../../../lib/init/express');
const testPathDir = path.resolve(__dirname, '../../mock/spec/periodic');
const folderStructure = require('../../../lib/init/folderStructure');
const initTestExpressPathDir = path.join(testPathDir, 'TestExpress');
const initTestExpressEndPathDir = path.join(testPathDir, 'TestExpressEnd');
const __STRUCTURE_DIR = path.resolve(__dirname, '../../../__STRUCTURE');
chai.use(require('sinon-chai'));
chai.use(require('chai-as-promised'));

describe('Periodic Init Express', function() {
  this.timeout(10000);
  before('initialize Express test periodic dir', (done) => {
    Promise.all([
        fs.ensureDir(initTestExpressPathDir),
        fs.ensureDir(initTestExpressEndPathDir),
        fs.copy(__STRUCTURE_DIR, initTestExpressPathDir)
      ])
      .then(() => {
        done();
      }).catch(done);
  });
  describe('configureViews', () => {
    it('should handle errors', () => {
      expect(express.configureViews()).to.eventually.be.rejected;
    });
    it('sets express non ejs view with proxy options', (done) => {
      const setSpy = sinon.spy();
      const enableSpy = sinon.spy();
      const engineSpy = sinon.spy();
      const mockThis = {
        config: {
          app_root: initTestExpressPathDir,
        },
        settings: {
          express: {
            config: {
              trust_proxy: true,
            },
            views: {
              engine: 'jsx',
              lru_cache: true,
              lru: 100,
              package: 'ejs',
            }
          }
        },
        app: {
          set: setSpy,
          enable: enableSpy,
          engine: engineSpy,
          // set: setSpy,
        },
      };
      express.configureViews.call(mockThis)
        .then(result => {
          expect(result).to.be.true;
          expect(setSpy.calledTwice).to.be.true;
          expect(enableSpy.calledOnce).to.be.true;
          expect(engineSpy.calledThrice).to.be.true;
          // expect(mockThis.config.time_end).to.be.a('number');
          done();
        })
        .catch(done);
    });
    it('sets express ejs view without proxy options', (done) => {
      const setSpy = sinon.spy();
      const enableSpy = sinon.spy();
      const engineSpy = sinon.spy();
      const mockThis = {
        config: {
          app_root: initTestExpressPathDir,
        },
        settings: {
          express: {
            config: {
              trust_proxy: false,
            },
            views: {
              // engine: 'ejs',
              lru_cache: false,
              lru: 100,
              package: 'ejs',
            }
          }
        },
        app: {
          set: setSpy,
          enable: enableSpy,
          engine: engineSpy,
          // set: setSpy,
        },
      };
      express.configureViews.call(mockThis)
        .then(result => {
          expect(result).to.be.true;
          expect(setSpy.calledTwice).to.be.true;
          expect(enableSpy.called).to.not.be.true;
          expect(engineSpy.calledThrice).to.be.true;
          // expect(mockThis.config.time_end).to.be.a('number');
          done();
        })
        .catch(done);
    });
  });
  describe('configureExpress', () => {
    it('has configurable flash settings', (done) => {
      const useSpy = sinon.spy();
      const mockThis = {
        config: {
          app_root: initTestExpressPathDir,
        },
        settings: {
          express: {
            use_flash: false,
            config: {
              trust_proxy: false,
            },
            body_parser: {
              urlencoded: {
                limit: '1mb',
                extended: true,
              },
              json: {
                limit: '1mb'
              },
            },
            cookies: {
              cookie_parser: 'defaultcookiejson',
            },
            views: {
              // engine: 'ejs',
              lru_cache: false,
              lru: 100,
              package: 'ejs',
            }
          }
        },
        app: {
          use: useSpy,
        },
      };
      express.configureExpress.call(mockThis)
        .then(result => {
          expect(result).to.be.true;
          expect(useSpy.called).to.be.true;
          // expect(enableSpy.called).to.not.be.true;
          // expect(engineSpy.calledThrice).to.be.true;
          // expect(mockThis.config.time_end).to.be.a('number');
          done();
        })
        .catch(done);
    });
    it('should handle errors', () => {
      expect(express.configureExpress()).to.eventually.be.rejected;
    });
  });
  describe('customizeExpress', () => {
    it('should handle errors', () => {
      expect(express.customizeExpress()).to.eventually.be.rejected;
    });
  });
  describe('staticCacheExpress', () => {
    it('has configurable static caching', (done) => {
      const useSpy = sinon.spy();
      const mockThis = {
        config: {
          app_root: initTestExpressPathDir,
        },
        settings: {
          express: {
            config: {
              use_static_caching: false,
            },
            body_parser: {
              urlencoded: {
                limit: '1mb',
                extended: true,
              },
              json: {
                limit: '1mb'
              },
            },
          },
        },
        app: {
          use: useSpy,
        },
      };
      express.staticCacheExpress.call(mockThis)
        .then(result => {
          expect(result).to.be.true;
          expect(useSpy.called).to.be.true;
          done();
        })
        .catch(done);
    });
    it('can use disabled static caching settings', (done) => {
      const useSpy = sinon.spy();
      const mockThis = {
        config: {
          app_root: initTestExpressPathDir,
        },
        settings: {
          express: {
            config: {
              use_static_caching: true,
            },
            body_parser: {
              urlencoded: {
                limit: '1mb',
                extended: true,
              },
              json: {
                limit: '1mb'
              },
            },
          },
        },
        app: {
          use: useSpy,
        },
      };
      express.staticCacheExpress.call(mockThis)
        .then(result => {
          expect(result).to.be.true;
          expect(useSpy.called).to.be.true;
          done();
        })
        .catch(done);
    });
    it('should handle errors', () => {
      expect(express.staticCacheExpress()).to.eventually.be.rejected;
    });
  });
  describe('compressExpress', () => {
    it('should handle errors', () => {
      expect(express.compressExpress()).to.eventually.be.rejected;
    });
    it('should use express compression', (done) => {
      const useSpy = sinon.spy();
      const mockThis = {
        config: {
          app_root: initTestExpressPathDir,
        },
        settings: {
          express: {
            config: {
              use_compression: true,
            },
          },
        },
        app: {
          use: useSpy,
        },
      };
      express.compressExpress.call(mockThis)
        .then(result => {
          expect(result).to.be.true;
          expect(useSpy.called).to.be.true;
          done();
        })
        .catch(done);
    });
    it('should not use express compression', (done) => {
      const useSpy = sinon.spy();
      const mockThis = {
        config: {
          app_root: initTestExpressPathDir,
        },
        settings: {
          express: {
            config: {
              use_compression: false,
            },
          }
        },
        app: {
          use: useSpy,
        },
      };
      express.compressExpress.call(mockThis)
        .then(result => {
          expect(result).to.be.true;
          expect(useSpy.called).to.not.be.true;
          done();
        })
        .catch(done);
    });
  });
  describe('logExpress', () => {
    it('should handle errors', () => {
      expect(express.logExpress()).to.eventually.be.rejected;
    });
    it('should generate the right colored response', () => {
      function getColoredResponse(options) {
        const { color, status } = options;
        return '\x1b[' + color + 'm' + status + '\x1b[90m';
      }
      expect(express.morganColors({}, { statusCode: 501 })).to.eql(getColoredResponse({ color: 31, status: 501 }));
      expect(express.morganColors({}, { statusCode: 401 })).to.eql(getColoredResponse({ color: 33, status: 401 }));
      expect(express.morganColors({}, { statusCode: 301 })).to.eql(getColoredResponse({ color: 36, status: 301 }));
      expect(express.morganColors({}, { statusCode: 200 })).to.eql(getColoredResponse({ color: 32, status: 200 }));
    });
    it('should use express debugging', (done) => {
      const useSpy = sinon.spy();
      const mockThis = {
        config: {
          app_root: initTestExpressPathDir,
        },
        settings: {
          express: {
            config: {
              debug: true,
            },
          },
        },
        app: {
          use: useSpy,
        },
      };
      express.logExpress.call(mockThis)
        .then(result => {
          expect(result).to.be.true;
          expect(useSpy.called).to.be.true;
          done();
        })
        .catch(done);
    });
    it('should not use express debugging', (done) => {
      const useSpy = sinon.spy();
      const mockThis = {
        config: {
          app_root: initTestExpressPathDir,
        },
        settings: {
          express: {
            config: {
              debug: false,
            },
          }
        },
        app: {
          use: useSpy,
        },
      };
      express.logExpress.call(mockThis)
        .then(result => {
          expect(result).to.be.true;
          expect(useSpy.called).to.not.be.true;
          done();
        })
        .catch(done);
    });
  });
  describe('expressSessions', () => {
    it('should handle errors', () => {
      expect(express.expressSessions()).to.eventually.be.rejected;
    });
    it('should skip sessions', () => {
      expect(express.skipSessions({
        req: {
          headers: {
            authorization: true,
          },
        }
      })).to.be.true;
      expect(express.skipSessions({
        req: {
          headers: {},
          query: {
            skip_session: true,
          },
        }
      })).to.be.true;
      expect(express.skipSessions({
        req: {
          headers: {},
          controllerData: {
            skip_session: true,
          },
        }
      })).to.be.true;
      expect(express.skipSessions({
        req: {
          headers: {},
          query: {},
          controllerData: {},
        }
      })).to.be.false;
    });
    it('should skip session middleware', (done) => {
      const mockThis = {
        config: {},
        settings: {
          express: {
            sessions: {
              enabled: false,
            },
          },
        },
      };
      express.expressSessions.call(mockThis)
        .then(result => {
          expect(result).to.be.true;
          done();
        })
        .catch(done);
    });
    it('should only support specific session stores', (done) => {
      const mockThis = {
        config: {},
        settings: {
          express: {
            sessions: {
              enabled: true,
              config: {
                secret: 'testsessions',
              },
            },
          },
        },
      };
      express.expressSessions.call(mockThis)
        .then(result => {
          done(new Error('Should be actually rejected'));
        })
        .catch(e => {
          expect(e.message).to.eql('Invalid express session type');
          done();
        });
    });
    it('should pass enabledSessionMiddleware', () => {
      const mockAuthReq = {
        headers: {
          authorization: true,
        },
      };
      const mockSessionReq = {
        headers: {},
        query: {},
        controllerData: {},
      };
      const mockRes = {};
      const sessionMiddlewareSpy = sinon.spy();
      const nextSpy = sinon.spy();
      express.enabledSessionMiddleware(sessionMiddlewareSpy, mockAuthReq, mockRes, nextSpy);
      expect(nextSpy.called).to.be.false;
      express.enabledSessionMiddleware(sessionMiddlewareSpy, mockSessionReq, mockRes, nextSpy);
      expect(sessionMiddlewareSpy.called).to.be.false;
    });
    it('should support loki session stores', (done) => {
      const useSpy = sinon.spy();
      const mockThis = {
        app: {
          use: useSpy,
        },
        config: {},
        settings: {
          express: {
            sessions: {
              enabled: true,
              type: 'loki',
              config: {
                secret: 'sessiontest',
              },
            },
            config: {
              csrf: true,
            },
          },
        },
      };
      express.expressSessions.call(mockThis)
        .then(result => {
          expect(result).to.be.true;
          expect(useSpy.called).to.be.true;
          done();
        })
        .catch(done);
    });
    it('should support mongo session stores', (done) => {
      const useSpy = sinon.spy();
      const mockThis = {
        app: {
          use: useSpy,
        },
        config: {},
        settings: {
          express: {
            sessions: {
              enabled: true,
              type: 'mongo',
              config: {
                secret: 'sessiontest',
              },
              store_settings: {
                url: 'mongodb://localhost:27017/session_db',
              },
            },
            config: {
              csrf: true,
            },
          },
        },
      };
      express.expressSessions.call(mockThis)
        .then(result => {
          expect(result).to.be.true;
          expect(useSpy.called).to.be.true;
          done();
        })
        .catch(done);
    });
    it('should support sql session stores', (done) => {
      const useSpy = sinon.spy();
      const mockThis = {
        app: {
          use: useSpy,
        },
        config: {},
        settings: {
          express: {
            sessions: {
              enabled: true,
              type: 'sql',
              config: {
                secret: 'sessiontest',
              },
              store_settings: {
                db: [
                  "travis_ci_test", //database
                  "", //username
                  "", //password
                  {
                    "dialect": "postgres",
                    "port": 5432,
                    "host": "127.0.0.1",
                    logging: false,
                  }, //options
                ],
                options: {},
                modelName: 'session',
              },
            },
            config: {
              csrf: true,
            },
          },
        },
      };
      express.expressSessions.call(mockThis)
        .then(result => {
          expect(result).to.be.true;
          expect(useSpy.called).to.be.true;
          done();
        })
        .catch(done);
    });
    it('should support redis session stores', (done) => {
      const useSpy = sinon.spy();
      const mockThis = {
        app: {
          use: useSpy,
        },
        config: {},
        settings: {
          express: {
            sessions: {
              enabled: true,
              type: 'redis',
              config: {
                secret: 'sessiontest',
              },
              store_settings: {
                // client An existing client
                host: 'localhost',
                port: 6379,
              },
            },
            config: {
              csrf: true,
            },
          },
        },
      };
      express.expressSessions.call(mockThis)
        .then(result => {
          expect(result).to.be.true;
          expect(useSpy.called).to.be.true;
          done();
        })
        .catch(done);
    });
  });
  describe('expressLocals', () => {
    it('should handle errors', () => {
      expect(express.expressLocals()).to.eventually.be.rejected;
    });
    it('useCSRFMiddleware handle CRSF', () => {
      const csrfSpy = sinon.spy();
      const mockReq = {
        csrfToken: csrfSpy,
      };
      const mockRes = {
        locals: {},
      };
      const nextSpy = sinon.spy();
      const mockCSRFThis = {
        settings: {
          express: {
            config: {
              csrf: true,
            },
          },
        },
      };
      const mockNoCSRFThis = {
        settings: {
          express: {
            config: {
              csrf: false,
            },
          },
        },
      };
      express.useCSRFMiddleware.call(mockCSRFThis, mockReq, mockRes, nextSpy);
      expect(csrfSpy.called).to.be.true;
      express.useCSRFMiddleware.call(mockNoCSRFThis, mockReq, mockRes, nextSpy);
      expect(nextSpy.called).to.be.true;
    });
    it('useLocalsMiddleware handle CRSF', () => {
      const mockReq = {
        headers: {},
        connection: {},
        user: {
          id: 1,
        },
      };
      const mockRes = {
        locals: {},
      };
      const nextSpy = sinon.spy();
      const mockThis = {
        app: {
          locals: {},
        },
      };
      express.useLocalsMiddleware.call(mockThis, mockReq, mockRes, nextSpy);
      expect(nextSpy.called).to.be.true;
      // expect(mockThis.app.locals.isLoggedIn({})).to.eql(mockReq.user);
      expect(mockRes.locals.isLoggedIn).to.be.true;
    });
  });
  describe('expressRouting', () => {
    it('should handle errors', () => {
      expect(express.expressRouting()).to.eventually.be.rejected;
    });
    it('should return a promise', () => {
      // this.settings.express.views.page_data
      // this.app.locals.page_data
      const mockThis = {
        app: {
          locals: {
            page_data:{},
          },
          get:()=>{},
        },
        routers:[],
        settings: {
          express: {
            views: {
              page_data:{},
            },
          },
        },
      };
      const expressPromise = express.expressRouting.call(mockThis);
      expect(expressPromise).to.be.an.instanceof(Promise);
      // console.log({ expressPromise });
      expect(expressPromise).to.eventually.be.fulfilled;
    });
  });
  describe('expressStatus', () => {
    it('should handle errors', () => {
      expect(express.expressStatus()).to.eventually.be.rejected;
    });
    it('should log current environment', (done) => {
      const debugSpy = sinon.spy();
      const warnSpy = sinon.spy();
      const mockThis = {
        environment: 'test',
        config: {
          debug: true,
        },
        logger: {
          debug: debugSpy,
          warn: warnSpy,
        },
        utilities: require('../../../lib/utilities'),
        settings: {
          application: {
            check_for_updates: true,
            version: '10.0.0',
          }
        }
      };
      express.expressStatus.call(mockThis)
        .then(result => {
          expect(result).to.be.true;
          expect(debugSpy.called).to.be.true;
          done();
        })
        .catch(done);
    });
    it('should handleLatestPeriodicVersionCheckError', () => {
      const warnSpy = sinon.spy();
      const mockThis = {
        logger: {
          warn: warnSpy,
        }
      }
      express.handleLatestPeriodicVersionCheckError.call(mockThis);
      expect(warnSpy.called).to.be.true;
    });
    it('should displayOutOfDatePeriodic', () => {
      const warnSpy = sinon.spy();
      const debugSpy = sinon.spy();
      const mockThis = {
        logger: {
          debug: debugSpy,
          warn: warnSpy,
        },
        settings: {
          application: {
            version: '10.0.0',
          },
        },
      }
      express.displayOutOfDatePeriodic.call(mockThis, { latestPeriodicVersion: '10.0.0' });
      expect(debugSpy.called).to.be.true;
    });
  });
  describe('expressErrors', () => {
    it('should handle errors', () => {
      expect(express.expressErrors()).to.eventually.be.rejected;
    });
    it('catchAllErrorMiddleware should skip middleware if no errors', () => {
      const statusSpy = sinon.spy();
      const sendSpy = sinon.spy();
      const mockReq = {
        query: {
          format: 'json',
        },
        is: (type) => true,
      };
      const mockRes = {
        status: statusSpy,
        send: sendSpy,
      };
      const mockError = new Error('error for test');
      const nextSpy = sinon.spy();
      const statusSpy2 = sinon.spy();
      const renderSpy = sinon.spy();
      const mockReqAllError = {
        query: {},
        is: (type) => false,
      }
      const mockResAllError = {
        status: statusSpy2,
        render: renderSpy,
      };
      express.catchAllErrorMiddleware(mockError, mockReq, mockRes, nextSpy);
      expect(sendSpy.called).to.be.true;
      expect(statusSpy.called).to.be.true;
      express.catchAllErrorMiddleware.call({
        settings: {
          express: {
            views: {
              custom_error_view: 'someerrorview',
            },
          },
        },
      }, mockError, mockReqAllError, mockResAllError, nextSpy);
      expect(statusSpy2.called).to.be.true;
      expect(renderSpy.called).to.be.true;
    });
    it('catchAllErrorMiddleware should respond with errors', () => {
      const nextSpy = sinon.spy();
      express.catchAllErrorMiddleware(false, null, null, nextSpy);
      expect(nextSpy.called).to.be.true;
    });
    it('errorLogMiddleware should set error on next call', () => {
      const nextSpy = sinon.spy();
      const errorSpy = sinon.spy();
      const mockReq = {
        headers: {},
        connection: {},
        user: {
          email: 'test@domain.tld',
        }
      };
      const mockReq2 = {
        headers: {},
        connection: {},
      };
      const mockRes = {};
      const mockError = new Error('middleware error test');
      express.errorLogMiddleware.call({
        logger: {
          error: errorSpy,
        },
      }, mockError, mockReq, mockRes, nextSpy);
      expect(nextSpy.called).to.be.true;
      expect(errorSpy.called).to.be.true;
      express.errorLogMiddleware.call({
        logger: {
          error: errorSpy,
        },
      }, mockError, mockReq2, mockRes, nextSpy);
    });
  });
  after('remove Express test periodic dir', (done) => {
    Promise.all([
        fs.remove(initTestExpressPathDir),
        fs.remove(initTestExpressEndPathDir),
      ])
      .then(() => {
        done();
      }).catch(done);
  });
});