'use strict';

require('should');

describe('API3 AAPS Patterns - Deduplication and Real-world Scenarios', function() {
  const self = this;
  const testConst = require('./fixtures/api3/const.json');
  const aapsPatterns = require('./fixtures/aaps-patterns.json');
  const instance = require('./fixtures/api3/instance');
  const authSubject = require('./fixtures/api3/authSubject');
  const opTools = require('../lib/api3/shared/operationTools');

  self.timeout(30000);

  before(async () => {
    self.instance = await instance.create({});
    self.app = self.instance.app;
    self.env = self.instance.env;

    let authResult = await authSubject(self.instance.ctx.authorization.storage, [
      'create',
      'update',
      'read',
      'delete',
      'all'
    ], self.instance.app);

    self.subject = authResult.subject;
    self.jwt = authResult.jwt;
    self.cache = self.instance.cacheMonitor;
  });

  after(() => {
    self.instance.ctx.bus.teardown();
  });

  beforeEach(() => {
    self.cache.clear();
  });

  describe('Deduplication Tests - identifier-based (device + date + eventType)', function() {
    const col = 'treatments';
    const url = `/api/v3/${col}`;

    async function deleteDoc(identifier) {
      self.cache.clear();
      await self.instance.delete(`${url}/${identifier}?permanent=true`, self.jwt.delete)
        .expect(200);
      self.cache.clear();
    }

    async function getDoc(identifier) {
      let res = await self.instance.get(`${url}/${identifier}`, self.jwt.read)
        .expect(200);
      return res.body.result;
    }

    it('returns 201 for new treatment with unique identifier and persists AAPS fields', async () => {
      const baseFixture = aapsPatterns.DEDUPLICATION_TESTS.ORIGINAL;
      const doc = {
        device: 'test-device-unique-' + Date.now(),
        date: Date.now(),
        app: testConst.TEST_APP,
        eventType: baseFixture.eventType,
        insulin: baseFixture.insulin,
        pumpId: baseFixture.pumpId,
        pumpType: baseFixture.pumpType,
        pumpSerial: baseFixture.pumpSerial,
        isValid: baseFixture.isValid
      };
      doc.identifier = opTools.calculateIdentifier(doc);

      let res = await self.instance.post(url, self.jwt.create)
        .send(doc)
        .expect(201);

      res.body.status.should.equal(201);
      res.body.identifier.should.equal(doc.identifier);

      const persisted = await getDoc(doc.identifier);
      persisted.pumpId.should.equal(baseFixture.pumpId);
      persisted.pumpType.should.equal(baseFixture.pumpType);
      persisted.pumpSerial.should.equal(baseFixture.pumpSerial);
      persisted.insulin.should.equal(baseFixture.insulin);

      await deleteDoc(doc.identifier);
    });

    it('returns 200 for duplicate with same device+date+eventType (deduplication)', async () => {
      const doc = {
        device: 'test-device-dedup-' + Date.now(),
        date: Date.now(),
        app: testConst.TEST_APP,
        eventType: 'Correction Bolus',
        insulin: 0.5,
        isValid: true
      };
      doc.identifier = opTools.calculateIdentifier(doc);

      let res1 = await self.instance.post(url, self.jwt.create)
        .send(doc)
        .expect(201);

      res1.body.status.should.equal(201);
      self.cache.clear();

      let res2 = await self.instance.post(url, self.jwt.update)
        .send(doc)
        .expect(200);

      res2.body.status.should.equal(200);
      res2.body.identifier.should.equal(doc.identifier);

      await deleteDoc(doc.identifier);
    });

    it('different pumpId but same device+date+eventType triggers dedup (pumpId not in identifier)', async () => {
      const baseTime = Date.now();
      const device = 'test-device-pumpid-' + baseTime;

      const doc1 = {
        device: device,
        date: baseTime,
        app: testConst.TEST_APP,
        eventType: 'Correction Bolus',
        insulin: 0.5,
        pumpId: 9998,
        pumpType: 'DANA_R',
        pumpSerial: '12345'
      };
      doc1.identifier = opTools.calculateIdentifier(doc1);

      const doc2 = {
        device: device,
        date: baseTime,
        app: testConst.TEST_APP,
        eventType: 'Correction Bolus',
        insulin: 0.5,
        pumpId: 9999,
        pumpType: 'DANA_R',
        pumpSerial: '12345'
      };
      doc2.identifier = opTools.calculateIdentifier(doc2);

      let res1 = await self.instance.post(url, self.jwt.create)
        .send(doc1)
        .expect(201);

      res1.body.status.should.equal(201);
      self.cache.clear();

      let res2 = await self.instance.post(url, self.jwt.update)
        .send(doc2)
        .expect(200);

      res2.body.status.should.equal(200);
      res2.body.identifier.should.equal(doc1.identifier);

      await deleteDoc(doc1.identifier);
    });

    it('different date creates new unique treatment (201)', async () => {
      const baseTime = Date.now();
      const device = 'test-device-date-' + baseTime;

      const doc1 = {
        device: device,
        date: baseTime,
        app: testConst.TEST_APP,
        eventType: 'Correction Bolus',
        insulin: 0.5
      };
      doc1.identifier = opTools.calculateIdentifier(doc1);

      const doc2 = {
        device: device,
        date: baseTime + 300000,
        app: testConst.TEST_APP,
        eventType: 'Correction Bolus',
        insulin: 0.5
      };
      doc2.identifier = opTools.calculateIdentifier(doc2);

      let res1 = await self.instance.post(url, self.jwt.create)
        .send(doc1)
        .expect(201);

      res1.body.status.should.equal(201);
      self.cache.clear();

      let res2 = await self.instance.post(url, self.jwt.create)
        .send(doc2)
        .expect(201);

      res2.body.status.should.equal(201);
      res2.body.identifier.should.not.equal(res1.body.identifier);

      await deleteDoc(doc1.identifier);
      await deleteDoc(doc2.identifier);
    });

    it('different eventType creates new unique treatment (201)', async () => {
      const baseTime = Date.now();
      const device = 'test-device-event-' + baseTime;

      const doc1 = {
        device: device,
        date: baseTime,
        app: testConst.TEST_APP,
        eventType: 'Correction Bolus',
        insulin: 0.5
      };
      doc1.identifier = opTools.calculateIdentifier(doc1);

      const doc2 = {
        device: device,
        date: baseTime,
        app: testConst.TEST_APP,
        eventType: 'Meal Bolus',
        insulin: 0.5
      };
      doc2.identifier = opTools.calculateIdentifier(doc2);

      let res1 = await self.instance.post(url, self.jwt.create)
        .send(doc1)
        .expect(201);

      res1.body.status.should.equal(201);
      self.cache.clear();

      let res2 = await self.instance.post(url, self.jwt.create)
        .send(doc2)
        .expect(201);

      res2.body.status.should.equal(201);
      res2.body.identifier.should.not.equal(res1.body.identifier);

      await deleteDoc(doc1.identifier);
      await deleteDoc(doc2.identifier);
    });

    it('rapid duplicate submissions result in single persisted document with latest srvModified', async () => {
      const baseTime = Date.now();
      const device = 'test-device-rapid-dedup-' + baseTime;

      const doc = {
        device: device,
        date: baseTime,
        app: testConst.TEST_APP,
        eventType: 'Correction Bolus',
        insulin: 0.5,
        pumpId: 8888,
        pumpType: aapsPatterns.PUMP_IDENTIFIERS.DANA_R.pumpType,
        pumpSerial: aapsPatterns.PUMP_IDENTIFIERS.DANA_R.pumpSerial
      };
      doc.identifier = opTools.calculateIdentifier(doc);

      let res1 = await self.instance.post(url, self.jwt.create)
        .send(doc)
        .expect(201);
      res1.body.status.should.equal(201);
      const firstSrvModified = res1.body.lastModified;
      self.cache.clear();

      await new Promise(resolve => setTimeout(resolve, 50));

      let res2 = await self.instance.post(url, self.jwt.update)
        .send(doc)
        .expect(200);
      res2.body.status.should.equal(200);
      res2.body.identifier.should.equal(doc.identifier);
      const secondSrvModified = res2.body.lastModified;
      secondSrvModified.should.be.greaterThan(firstSrvModified,
        'Second srvModified should be strictly greater than first');
      self.cache.clear();

      await new Promise(resolve => setTimeout(resolve, 50));

      let res3 = await self.instance.post(url, self.jwt.update)
        .send(doc)
        .expect(200);
      res3.body.status.should.equal(200);
      res3.body.identifier.should.equal(doc.identifier);
      const thirdSrvModified = res3.body.lastModified;
      thirdSrvModified.should.be.greaterThan(secondSrvModified,
        'Third srvModified should be strictly greater than second');
      self.cache.clear();

      const persisted = await getDoc(doc.identifier);
      persisted.identifier.should.equal(doc.identifier);
      persisted.pumpId.should.equal(8888);
      persisted.srvModified.should.be.greaterThan(persisted.srvCreated,
        'Persisted srvModified should be strictly greater than srvCreated after updates');

      persisted.srvModified.should.equal(thirdSrvModified,
        'Persisted srvModified must exactly match the lastModified from the final API response');

      let searchByIdentifier = await self.instance.get(`${url}?identifier=${doc.identifier}`, self.jwt.read)
        .expect(200);
      searchByIdentifier.body.result.length.should.equal(1,
        'Exactly one document should exist when searching by identifier');
      searchByIdentifier.body.result[0].identifier.should.equal(doc.identifier);
      searchByIdentifier.body.result[0].srvModified.should.equal(thirdSrvModified);

      let searchByDeviceDate = await self.instance.get(`${url}?device=${device}&date$eq=${baseTime}`, self.jwt.read)
        .expect(200);
      searchByDeviceDate.body.result.length.should.equal(1,
        'Exactly one document should exist when searching by device + date');
      searchByDeviceDate.body.result[0].identifier.should.equal(doc.identifier);
      searchByDeviceDate.body.result[0].srvModified.should.equal(thirdSrvModified);

      await deleteDoc(doc.identifier);
    });

    it('full AAPS fixture payload with created_at and dateString persists correctly', async () => {
      const baseTime = Date.now();
      const baseFixture = aapsPatterns.DEDUPLICATION_TESTS.ORIGINAL;

      const doc = {
        device: 'test-device-full-fixture-' + baseTime,
        date: baseTime,
        created_at: new Date(baseTime).toISOString(),
        dateString: new Date(baseTime).toISOString(),
        app: testConst.TEST_APP,
        eventType: baseFixture.eventType,
        insulin: baseFixture.insulin,
        pumpId: baseFixture.pumpId,
        pumpType: baseFixture.pumpType,
        pumpSerial: baseFixture.pumpSerial,
        isValid: baseFixture.isValid,
        isSMB: false,
        type: 'NORMAL'
      };
      doc.identifier = opTools.calculateIdentifier(doc);

      let res = await self.instance.post(url, self.jwt.create)
        .send(doc)
        .expect(201);

      res.body.status.should.equal(201);
      self.cache.clear();

      const persisted = await getDoc(doc.identifier);
      persisted.identifier.should.equal(doc.identifier);
      persisted.pumpId.should.equal(baseFixture.pumpId);
      persisted.pumpType.should.equal(baseFixture.pumpType);
      persisted.pumpSerial.should.equal(baseFixture.pumpSerial);
      persisted.insulin.should.equal(baseFixture.insulin);
      persisted.eventType.should.equal(baseFixture.eventType);
      persisted.should.have.property('srvCreated');
      persisted.should.have.property('srvModified');

      await deleteDoc(doc.identifier);
    });
  });

  describe('SMB Burst Pattern - rapid sequential micro-boluses', function() {
    const col = 'treatments';
    const url = `/api/v3/${col}`;

    async function deleteDoc(identifier) {
      self.cache.clear();
      await self.instance.delete(`${url}/${identifier}?permanent=true`, self.jwt.delete)
        .expect(200);
      self.cache.clear();
    }

    async function getDoc(identifier) {
      let res = await self.instance.get(`${url}/${identifier}`, self.jwt.read)
        .expect(200);
      return res.body.result;
    }

    it('handles sequential SMB corrections with full AAPS-realistic payloads', async () => {
      const baseTime = Date.now();
      const device = 'test-device-smb-' + baseTime;
      const identifiers = [];
      const smbFixtures = aapsPatterns.SMB_BURSTS.CORRECTION_SEQUENCE;

      for (let i = 0; i < 3; i++) {
        const fixtureSmb = smbFixtures[i];
        const smb = {
          device: device,
          date: baseTime + (i * 300000),
          created_at: new Date(baseTime + (i * 300000)).toISOString(),
          app: testConst.TEST_APP,
          eventType: fixtureSmb.eventType,
          insulin: fixtureSmb.insulin,
          isSMB: fixtureSmb.isSMB,
          pumpId: fixtureSmb.pumpId,
          pumpType: fixtureSmb.pumpType,
          pumpSerial: fixtureSmb.pumpSerial,
          type: fixtureSmb.type || 'NORMAL',
          isValid: fixtureSmb.isValid
        };
        smb.identifier = opTools.calculateIdentifier(smb);

        let res = await self.instance.post(url, self.jwt.create)
          .send(smb)
          .expect(201);

        res.body.status.should.equal(201);
        identifiers.push(res.body.identifier);
        self.cache.clear();

        const persisted = await getDoc(res.body.identifier);
        persisted.pumpId.should.equal(fixtureSmb.pumpId);
        persisted.isSMB.should.equal(true);
        persisted.insulin.should.equal(fixtureSmb.insulin);
        self.cache.clear();
      }

      const uniqueIds = [...new Set(identifiers)];
      uniqueIds.length.should.equal(3);

      for (const id of identifiers) {
        await deleteDoc(id);
      }
    });
  });

  describe('Meal Scenario - carbs, bolus wizard, meal bolus', function() {
    const col = 'treatments';
    const url = `/api/v3/${col}`;

    async function deleteDoc(identifier) {
      self.cache.clear();
      await self.instance.delete(`${url}/${identifier}?permanent=true`, self.jwt.delete)
        .expect(200);
      self.cache.clear();
    }

    async function getDoc(identifier) {
      let res = await self.instance.get(`${url}/${identifier}`, self.jwt.read)
        .expect(200);
      return res.body.result;
    }

    it('creates all meal-related treatments with full AAPS payloads and verifies persisted fields', async () => {
      const baseTime = Date.now();
      const device = 'test-device-meal-' + baseTime;
      const identifiers = [];
      const mealFixtures = aapsPatterns.MEAL_SCENARIO;

      const carbsFixture = mealFixtures.CARB_ENTRY;
      const carbs = {
        device: device,
        date: baseTime,
        created_at: new Date(baseTime).toISOString(),
        app: testConst.TEST_APP,
        eventType: carbsFixture.eventType,
        carbs: carbsFixture.carbs,
        isValid: carbsFixture.isValid
      };
      carbs.identifier = opTools.calculateIdentifier(carbs);

      let res1 = await self.instance.post(url, self.jwt.create)
        .send(carbs)
        .expect(201);
      identifiers.push(res1.body.identifier);
      self.cache.clear();

      const persistedCarbs = await getDoc(res1.body.identifier);
      persistedCarbs.carbs.should.equal(carbsFixture.carbs);
      persistedCarbs.eventType.should.equal(carbsFixture.eventType);
      persistedCarbs.should.have.property('srvCreated');
      self.cache.clear();

      const wizardFixture = mealFixtures.BOLUS_WIZARD;
      const wizard = {
        device: device,
        date: baseTime + 1000,
        created_at: new Date(baseTime + 1000).toISOString(),
        app: testConst.TEST_APP,
        eventType: wizardFixture.eventType,
        glucose: wizardFixture.glucose,
        glucoseType: wizardFixture.glucoseType,
        carbs: wizardFixture.carbs,
        insulin: wizardFixture.insulin
      };
      wizard.identifier = opTools.calculateIdentifier(wizard);

      let res2 = await self.instance.post(url, self.jwt.create)
        .send(wizard)
        .expect(201);
      identifiers.push(res2.body.identifier);
      self.cache.clear();

      const persistedWizard = await getDoc(res2.body.identifier);
      persistedWizard.glucose.should.equal(wizardFixture.glucose);
      persistedWizard.insulin.should.equal(wizardFixture.insulin);
      persistedWizard.should.have.property('srvCreated');
      self.cache.clear();

      const bolusFixture = mealFixtures.MEAL_BOLUS;
      const bolus = {
        device: device,
        date: baseTime + 2000,
        created_at: new Date(baseTime + 2000).toISOString(),
        app: testConst.TEST_APP,
        eventType: bolusFixture.eventType,
        insulin: bolusFixture.insulin,
        pumpId: bolusFixture.pumpId,
        pumpType: bolusFixture.pumpType,
        pumpSerial: bolusFixture.pumpSerial
      };
      bolus.identifier = opTools.calculateIdentifier(bolus);

      let res3 = await self.instance.post(url, self.jwt.create)
        .send(bolus)
        .expect(201);
      identifiers.push(res3.body.identifier);
      self.cache.clear();

      const persistedBolus = await getDoc(res3.body.identifier);
      persistedBolus.pumpId.should.equal(bolusFixture.pumpId);
      persistedBolus.pumpType.should.equal(bolusFixture.pumpType);
      persistedBolus.pumpSerial.should.equal(bolusFixture.pumpSerial);
      persistedBolus.insulin.should.equal(bolusFixture.insulin);
      persistedBolus.should.have.property('srvCreated');
      self.cache.clear();

      const uniqueIds = [...new Set(identifiers)];
      uniqueIds.length.should.equal(3);

      for (const id of identifiers) {
        await deleteDoc(id);
      }
    });
  });

  describe('SGV Entry Pattern - high-frequency glucose readings', function() {
    const col = 'entries';
    const url = `/api/v3/${col}`;

    async function deleteDoc(identifier) {
      self.cache.clear();
      await self.instance.delete(`${url}/${identifier}?permanent=true`, self.jwt.delete)
        .expect(200);
      self.cache.clear();
    }

    async function getDoc(identifier) {
      let res = await self.instance.get(`${url}/${identifier}`, self.jwt.read)
        .expect(200);
      return res.body.result;
    }

    it('handles rapid sequential SGV entries with full AAPS payloads and verifies persisted fields', async () => {
      const baseTime = Date.now();
      const device = 'test-device-sgv-' + baseTime;
      const identifiers = [];
      const sgvFixtures = aapsPatterns.SGV_ENTRIES.HIGH_FREQUENCY_BATCH;

      for (let i = 0; i < 3; i++) {
        const fixtureEntry = sgvFixtures[i];
        const sgv = {
          device: device,
          date: baseTime + (i * 300000),
          dateString: new Date(baseTime + (i * 300000)).toISOString(),
          app: testConst.TEST_APP,
          type: fixtureEntry.type,
          sgv: fixtureEntry.sgv,
          direction: fixtureEntry.direction,
          utcOffset: fixtureEntry.utcOffset
        };
        sgv.identifier = opTools.calculateIdentifier(sgv);

        let res = await self.instance.post(url, self.jwt.create)
          .send(sgv)
          .expect(201);

        res.body.status.should.equal(201);
        identifiers.push(res.body.identifier);
        self.cache.clear();

        const persisted = await getDoc(res.body.identifier);
        persisted.sgv.should.equal(fixtureEntry.sgv);
        persisted.direction.should.equal(fixtureEntry.direction);
        persisted.should.have.property('srvCreated');
        persisted.should.have.property('srvModified');
        self.cache.clear();
      }

      const uniqueIds = [...new Set(identifiers)];
      uniqueIds.length.should.equal(3);

      for (const id of identifiers) {
        await deleteDoc(id);
      }
    });
  });
});
