import {expect, assert} from 'chai';
import {spec} from 'modules/kargoBidAdapter';
import {config} from 'src/config';

describe('kargo adapter tests', function () {
  var sandbox, clock, frozenNow = new Date();

  beforeEach(function () {
    sandbox = sinon.sandbox.create();
    clock = sinon.useFakeTimers(frozenNow.getTime());
  });

  afterEach(function () {
    sandbox.restore();
    clock.restore();
  });

  describe('bid request validity', function() {
    it('passes when the bid includes a placement ID', function() {
      assert(spec.isBidRequestValid({params: {placementId: 'foo'}}) === true);
    });

    it('fails when the bid does not include a placement ID', function() {
      assert(spec.isBidRequestValid({params: {}}) === false);
    });

    it('fails when bid is falsey', function() {
      assert(spec.isBidRequestValid() === false);
    });

    it('fails when the bid has no params at all', function() {
      assert(spec.isBidRequestValid({}) === false);
    });
  });

  describe('build request', function() {
    var bids, undefinedCurrency, noAdServerCurrency, cookies = [], localStorageItems = [], sessionIds = [], requestCount = 0;

    beforeEach(function () {
      undefinedCurrency = false;
      noAdServerCurrency = false;
      sandbox.stub(config, 'getConfig').callsFake(function(key) {
        if (key === 'currency') {
          if (undefinedCurrency) {
            return undefined;
          }
          if (noAdServerCurrency) {
            return {};
          }
          return {adServerCurrency: 'USD'};
        }
        throw new Error(`Config stub incomplete! Missing key "${key}"`)
      });

      bids = [
        {
          params: {
            placementId: 'foo'
          },
          bidId: 1,
          userId: {
            tdid: 'fake-tdid'
          },
          sizes: [[320, 50], [300, 250], [300, 600]]
        },
        {
          params: {
            placementId: 'bar'
          },
          bidId: 2,
          sizes: [[320, 50], [300, 250], [300, 600]]
        },
        {
          params: {
            placementId: 'bar'
          },
          bidId: 3,
          sizes: [[320, 50], [300, 250], [300, 600]]
        }
      ];
    });

    afterEach(function () {
      for (let key in cookies) {
        let cookie = cookies[key];
        removeCookie(cookie);
      }

      for (let key in localStorageItems) {
        let localStorageItem = localStorageItems[key];
        localStorage.removeItem(localStorageItem);
      }

      cookies.length = 0;
      localStorageItems.length = 0;
    });

    function setCookie(cname, cvalue, exdays = 1) {
      _setCookie(cname, cvalue, exdays);
      cookies.push(cname);
    }

    function removeCookie(cname) {
      _setCookie(cname, '', -1);
    }

    function _setCookie(cname, cvalue, exdays = 1) {
      var d = new Date(),
        expires;

      d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
      expires = `expires=${d.toUTCString()}`;
      document.cookie = `${cname}=${cvalue};${expires};path=/`;
    }

    function setLocalStorageItem(name, val) {
      localStorage.setItem(name, val);
      localStorageItems.push(name);
    }

    function simulateNoLocalStorage() {
      return sandbox.stub(localStorage, 'getItem').throws();
    }

    function simulateNoCurrencyObject() {
      undefinedCurrency = true;
      noAdServerCurrency = false;
    }

    function simulateNoAdServerCurrency() {
      undefinedCurrency = false;
      noAdServerCurrency = true;
    }

    function initializeKruxUser() {
      setLocalStorageItem('kxkar_user', 'rsgr9pnij');
    }

    function initializeKruxSegments() {
      setLocalStorageItem('kxkar_segs', 'qv9v984dy,rpx2gy365,qrd5u4axv,rnub9nmtd,reha00jnu');
    }

    function getKrgCrb() {
      return 'eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwidXNlcklkIjoiNWYxMDg4MzEtMzAyZC0xMWU3LWJmNmItNDU5NWFjZDNiZjZjIiwiY2xpZW50SWQiOiIyNDEwZDhmMi1jMTExLTQ4MTEtODhhNS03YjVlMTkwZTQ3NWYiLCJvcHRPdXQiOmZhbHNlLCJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9';
    }

    function getKrgCrbOldStyle() {
      return '%7B%22v%22%3A%22eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwidXNlcklkIjoiNWYxMDg4MzEtMzAyZC0xMWU3LWJmNmItNDU5NWFjZDNiZjZjIiwiY2xpZW50SWQiOiIyNDEwZDhmMi1jMTExLTQ4MTEtODhhNS03YjVlMTkwZTQ3NWYiLCJvcHRPdXQiOmZhbHNlLCJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9%22%7D';
    }

    function initializeKrgCrb(cookieOnly) {
      if (!cookieOnly) {
        setLocalStorageItem('krg_crb', getKrgCrb());
      }
      setCookie('krg_crb', getKrgCrbOldStyle());
    }

    function getInvalidKrgCrbType1() {
      return 'invalid-krg-crb';
    }

    function initializeInvalidKrgCrbType1() {
      setLocalStorageItem('krg_crb', getInvalidKrgCrbType1());
    }

    function initializeInvalidKrgCrbType1Cookie() {
      setCookie('krg_crb', getInvalidKrgCrbType1());
    }

    function getInvalidKrgCrbType2() {
      return 'Ly8v';
    }

    function getInvalidKrgCrbType2OldStyle() {
      return '%7B%22v%22%3A%22%26%26%26%26%26%26%22%7D';
    }

    function initializeInvalidKrgCrbType2() {
      setLocalStorageItem('krg_crb', getInvalidKrgCrbType2());
    }

    function initializeInvalidKrgCrbType2Cookie() {
      setCookie('krg_crb', getInvalidKrgCrbType2OldStyle());
    }

    function getInvalidKrgCrbType3OldStyle() {
      return '%7B%22v%22%3A%22Ly8v%22%7D';
    }

    function initializeInvalidKrgCrbType3Cookie() {
      setCookie('krg_crb', getInvalidKrgCrbType3OldStyle());
    }

    function getInvalidKrgCrbType4OldStyle() {
      return '%7B%22v%22%3A%22bnVsbA%3D%3D%22%7D';
    }

    function initializeInvalidKrgCrbType4Cookie() {
      setCookie('krg_crb', getInvalidKrgCrbType4OldStyle());
    }

    function getEmptyKrgCrb() {
      return 'eyJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9';
    }

    function getEmptyKrgCrbOldStyle() {
      return '%7B%22v%22%3A%22eyJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9%22%7D';
    }

    function initializeEmptyKrgCrb() {
      setLocalStorageItem('krg_crb', getEmptyKrgCrb());
    }

    function initializeEmptyKrgCrbCookie() {
      setCookie('krg_crb', getEmptyKrgCrbOldStyle());
    }

    function getSessionId() {
      return spec._getSessionId();
    }

    function getExpectedKrakenParams(excludeUserIds, excludeKrux, expectedRawCRB, expectedRawCRBCookie) {
      var base = {
        timeout: 200,
        requestCount: requestCount++,
        currency: 'USD',
        cpmGranularity: 1,
        timestamp: frozenNow.getTime(),
        cpmRange: {
          floor: 0,
          ceil: 20
        },
        bidIDs: {
          1: 'foo',
          2: 'bar',
          3: 'bar'
        },
        bidSizes: {
          1: [[320, 50], [300, 250], [300, 600]],
          2: [[320, 50], [300, 250], [300, 600]],
          3: [[320, 50], [300, 250], [300, 600]]
        },
        userIDs: {
          kargoID: '5f108831-302d-11e7-bf6b-4595acd3bf6c',
          clientID: '2410d8f2-c111-4811-88a5-7b5e190e475f',
          tdID: 'fake-tdid',
          crbIDs: {
            2: '82fa2555-5969-4614-b4ce-4dcf1080e9f9',
            16: 'VoxIk8AoJz0AAEdCeyAAAAC2&502',
            23: 'd2a855a5-1b1c-4300-940e-a708fa1f1bde',
            24: 'VoxIk8AoJz0AAEdCeyAAAAC2&502',
            25: '5ee24138-5e03-4b9d-a953-38e833f2849f',
            '2_80': 'd2a855a5-1b1c-4300-940e-a708fa1f1bde',
            '2_93': '5ee24138-5e03-4b9d-a953-38e833f2849f'
          },
          optOut: false
        },
        krux: {
          userID: 'rsgr9pnij',
          segments: [
            'qv9v984dy',
            'rpx2gy365',
            'qrd5u4axv',
            'rnub9nmtd',
            'reha00jnu'
          ]
        },
        pageURL: window.location.href,
        prebidRawBidRequests: [
          {
            bidId: 1,
            params: {
              placementId: 'foo'
            },
            userId: {
              tdid: 'fake-tdid'
            },
            sizes: [[320, 50], [300, 250], [300, 600]]
          },
          {
            bidId: 2,
            params: {
              placementId: 'bar'
            },
            sizes: [[320, 50], [300, 250], [300, 600]]
          },
          {
            bidId: 3,
            params: {
              placementId: 'bar'
            },
            sizes: [[320, 50], [300, 250], [300, 600]]
          }
        ],
        rawCRB: expectedRawCRBCookie,
        rawCRBLocalStorage: expectedRawCRB
      };

      if (excludeUserIds === true) {
        base.userIDs = {
          crbIDs: {}
        };
        delete base.prebidRawBidRequests[0].userId.tdid;
      }

      if (excludeKrux) {
        base.krux = {
          userID: null,
          segments: []
        };
      }

      return base;
    }

    function testBuildRequests(excludeTdid, expected) {
      var clonedBids = JSON.parse(JSON.stringify(bids));
      if (excludeTdid) {
        delete clonedBids[0].userId.tdid;
      }
      var request = spec.buildRequests(clonedBids, {timeout: 200, foo: 'bar'});
      expected.sessionId = getSessionId();
      sessionIds.push(expected.sessionId);
      var krakenParams = JSON.parse(decodeURIComponent(request.data.slice(5)));
      expect(request.data.slice(0, 5)).to.equal('json=');
      expect(request.url).to.equal('https://krk.kargo.com/api/v2/bid');
      expect(request.method).to.equal('GET');
      expect(request.currency).to.equal('USD');
      expect(request.timeout).to.equal(200);
      expect(request.foo).to.equal('bar');
      expect(krakenParams).to.deep.equal(expected);
      // Make sure session ID stays the same across requests simulating multiple auctions on one page load
      for (let i in sessionIds) {
        if (i == 0) {
          continue;
        }
        let sessionId = sessionIds[i];
        expect(sessionIds[0]).to.equal(sessionId);
      }
    }

    it('works when all params and localstorage and cookies are correctly set', function() {
      initializeKruxUser();
      initializeKruxSegments();
      initializeKrgCrb();
      testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle()));
    });

    it('works when all params and cookies are correctly set but no localstorage', function() {
      initializeKruxUser();
      initializeKruxSegments();
      initializeKrgCrb(true);
      testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, null, getKrgCrbOldStyle()));
    });

    it('gracefully handles nothing being set', function() {
      testBuildRequests(true, getExpectedKrakenParams(true, true, null, null));
    });

    it('gracefully handles browsers without localStorage', function() {
      simulateNoLocalStorage();
      testBuildRequests(true, getExpectedKrakenParams(true, true, null, null));
    });

    it('handles empty yet valid Kargo CRB', function() {
      initializeKruxUser();
      initializeKruxSegments();
      initializeEmptyKrgCrb();
      initializeEmptyKrgCrbCookie();
      testBuildRequests(true, getExpectedKrakenParams(true, undefined, getEmptyKrgCrb(), getEmptyKrgCrbOldStyle()));
    });

    it('handles broken Kargo CRBs where base64 encoding is invalid', function() {
      initializeKruxUser();
      initializeKruxSegments();
      initializeInvalidKrgCrbType1();
      testBuildRequests(true, getExpectedKrakenParams(true, undefined, getInvalidKrgCrbType1(), null));
    });

    it('handles broken Kargo CRBs where top level JSON is invalid on cookie', function() {
      initializeKruxUser();
      initializeKruxSegments();
      initializeInvalidKrgCrbType1Cookie();
      testBuildRequests(true, getExpectedKrakenParams(true, undefined, null, getInvalidKrgCrbType1()));
    });

    it('handles broken Kargo CRBs where decoded JSON is invalid', function() {
      initializeKruxUser();
      initializeKruxSegments();
      initializeInvalidKrgCrbType2();
      testBuildRequests(true, getExpectedKrakenParams(true, undefined, getInvalidKrgCrbType2(), null));
    });

    it('handles broken Kargo CRBs where inner base 64 is invalid on cookie', function() {
      initializeKruxUser();
      initializeKruxSegments();
      initializeInvalidKrgCrbType2Cookie();
      testBuildRequests(true, getExpectedKrakenParams(true, undefined, null, getInvalidKrgCrbType2OldStyle()));
    });

    it('handles broken Kargo CRBs where inner JSON is invalid on cookie', function() {
      initializeKruxUser();
      initializeKruxSegments();
      initializeInvalidKrgCrbType3Cookie();
      testBuildRequests(true, getExpectedKrakenParams(true, undefined, null, getInvalidKrgCrbType3OldStyle()));
    });

    it('handles broken Kargo CRBs where inner JSON is falsey', function() {
      initializeKruxUser();
      initializeKruxSegments();
      initializeInvalidKrgCrbType4Cookie();
      testBuildRequests(true, getExpectedKrakenParams(true, undefined, null, getInvalidKrgCrbType4OldStyle()));
    });

    it('handles a non-existant currency object on the config', function() {
      simulateNoCurrencyObject();
      initializeKruxUser();
      initializeKruxSegments();
      initializeKrgCrb();
      testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle()));
    });

    it('handles no ad server currency being set on the currency object in the config', function() {
      simulateNoAdServerCurrency();
      initializeKruxUser();
      initializeKruxSegments();
      initializeKrgCrb();
      testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle()));
    });
  });

  describe('response handler', function() {
    it('handles bid responses', function() {
      var resp = spec.interpretResponse({body: {
        1: {
          id: 'foo',
          cpm: 3,
          adm: '<div id="1"></div>',
          width: 320,
          height: 50
        },
        2: {
          id: 'bar',
          cpm: 2.5,
          adm: '<div id="2"></div>',
          width: 300,
          height: 250,
          targetingCustom: 'dmpmptest1234'
        },
        3: {
          id: 'bar',
          cpm: 2.5,
          adm: '<div id="2"></div>',
          width: 300,
          height: 250
        }
      }}, {
        currency: 'USD',
        bids: [{
          bidId: 1,
          params: {
            placementId: 'foo'
          }
        }, {
          bidId: 2,
          params: {
            placementId: 'bar'
          }
        }, {
          bidId: 3,
          params: {
            placementId: 'bar'
          }
        }]
      });
      var expectation = [{
        requestId: '1',
        cpm: 3,
        width: 320,
        height: 50,
        ad: '<div id="1"></div>',
        ttl: 300,
        creativeId: 'foo',
        dealId: undefined,
        netRevenue: true,
        currency: 'USD'
      }, {
        requestId: '2',
        cpm: 2.5,
        width: 300,
        height: 250,
        ad: '<div id="2"></div>',
        ttl: 300,
        creativeId: 'bar',
        dealId: 'dmpmptest1234',
        netRevenue: true,
        currency: 'USD'
      }, {
        requestId: '3',
        cpm: 2.5,
        width: 300,
        height: 250,
        ad: '<div id="2"></div>',
        ttl: 300,
        creativeId: 'bar',
        dealId: undefined,
        netRevenue: true,
        currency: 'USD'
      }];
      expect(resp).to.deep.equal(expectation);
    });
  });

  describe('user sync handler', function() {
    const clientId = '74c81cbb-7d07-46d9-be9b-68ccb291c949';
    var shouldSimulateOutdatedBrowser, crb, isActuallyOutdatedBrowser;

    beforeEach(() => {
      crb = {};
      shouldSimulateOutdatedBrowser = false;
      isActuallyOutdatedBrowser = false;

      // IE11 fails these tests in the Prebid test suite. Since this
      // browser won't support any of this stuff we expect all user
      // syncing to fail gracefully. Kargo is mobile only, so this
      // doesn't really matter.
      if (!window.crypto) {
        isActuallyOutdatedBrowser = true;
      } else {
        sandbox.stub(crypto, 'getRandomValues').callsFake(function(buf) {
          if (shouldSimulateOutdatedBrowser) {
            throw new Error('Could not generate random values');
          }
          var bytes = [50, 5, 232, 133, 141, 55, 49, 57, 244, 126, 248, 44, 255, 38, 128, 0];
          for (var i = 0; i < bytes.length; i++) {
            buf[i] = bytes[i];
          }
          return buf;
        });
      }

      sandbox.stub(spec, '_getCrb').callsFake(function() {
        return crb;
      });
    });

    function getUserSyncsWhenAllowed() {
      return spec.getUserSyncs({iframeEnabled: true});
    }

    function getUserSyncsWhenForbidden() {
      return spec.getUserSyncs({});
    }

    function turnOnClientId() {
      crb.clientId = clientId;
    }

    function simulateOutdatedBrowser() {
      shouldSimulateOutdatedBrowser = true;
    }

    function getSyncUrl(index) {
      return {
        type: 'iframe',
        url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}`
      };
    }

    function getSyncUrls() {
      var syncs = [];
      for (var i = 0; i < 5; i++) {
        syncs[i] = getSyncUrl(i);
      }
      return syncs;
    }

    function safelyRun(runExpectation) {
      if (isActuallyOutdatedBrowser) {
        expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty;
      } else {
        runExpectation();
      }
    }

    it('handles user syncs when there is a client id', function() {
      turnOnClientId();
      safelyRun(() => expect(getUserSyncsWhenAllowed()).to.deep.equal(getSyncUrls()));
    });

    it('no user syncs when there is no client id', function() {
      safelyRun(() => expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty);
    });

    it('no user syncs when there is outdated browser', function() {
      turnOnClientId();
      simulateOutdatedBrowser();
      safelyRun(() => expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty);
    });

    it('no user syncs when no iframe syncing allowed', function() {
      turnOnClientId();
      safelyRun(() => expect(getUserSyncsWhenForbidden()).to.be.an('array').that.is.empty);
    });
  });
});
