ModelQuery = require '../../src/flux/models/query'
{Matcher} = require '../../src/flux/attributes'
Thread = require '../../src/flux/models/thread'
Namespace = require '../../src/flux/models/namespace'

describe "ModelQuery", ->
  beforeEach ->
    @db = {}

  describe "where", ->
    beforeEach ->
      @q = new ModelQuery(Thread, @db)
      @m1 = Thread.attributes.id.equal(4)
      @m2 = Thread.attributes.tags.contains('inbox')

    it "should accept an array of Matcher objects", ->
      @q.where([@m1,@m2])
      expect(@q._matchers.length).toBe(2)
      expect(@q._matchers[0]).toBe(@m1)
      expect(@q._matchers[1]).toBe(@m2)

    it "should accept a single Matcher object", ->
      @q.where(@m1)
      expect(@q._matchers.length).toBe(1)
      expect(@q._matchers[0]).toBe(@m1)

    it "should append to any existing where clauses", ->
      @q.where(@m1)
      @q.where(@m2)
      expect(@q._matchers.length).toBe(2)
      expect(@q._matchers[0]).toBe(@m1)
      expect(@q._matchers[1]).toBe(@m2)

    it "should accept a shorthand format", ->
      @q.where({id: 4, lastMessageTimestamp: 1234})
      expect(@q._matchers.length).toBe(2)
      expect(@q._matchers[0].attr.modelKey).toBe('id')
      expect(@q._matchers[0].comparator).toBe('=')
      expect(@q._matchers[0].val).toBe(4)

    it "should return the query so it can be chained", ->
      expect(@q.where({id: 4})).toBe(@q)

    it "should immediately raise an exception if an un-queryable attribute is specified", ->
      expect(-> @q.where({snippet: 'My Snippet'})).toThrow()

    it "should immediately raise an exception if a non-existent attribute is specified", ->
      expect(-> @q.where({looksLikeADuck: 'of course'})).toThrow()

  describe "order", ->
    beforeEach ->
      @q = new ModelQuery(Thread, @db)
      @o1 = Thread.attributes.lastMessageTimestamp.descending()
      @o2 = Thread.attributes.subject.descending()

    it "should accept an array of SortOrders", ->
      @q.order([@o1,@o2])
      expect(@q._orders.length).toBe(2)

    it "should accept a single SortOrder object", ->
      @q.order(@o2)
      expect(@q._orders.length).toBe(1)

    it "should extend any existing ordering", ->
      @q.order(@o1)
      @q.order(@o2)
      expect(@q._orders.length).toBe(2)
      expect(@q._orders[0]).toBe(@o1)
      expect(@q._orders[1]).toBe(@o2)

    it "should return the query so it can be chained", ->
      expect(@q.order(@o2)).toBe(@q)

  describe "sql", ->
    beforeEach ->
      @runScenario = (klass, scenario) ->
        q = new ModelQuery(klass, @db)
        Matcher.muid = 1
        scenario.builder(q)
        expect(q.sql().trim()).toBe(scenario.sql.trim())

    it "should correctly generate queries with multiple where clauses", ->
      @runScenario Namespace,
        builder: (q) -> q.where({emailAddress: 'ben@inboxapp.com'}).where({id: 2})
        sql: "SELECT `Namespace`.`data` FROM `Namespace`  \
              WHERE `email_address` = 'ben@inboxapp.com' AND `id` = 2"

    it "should correctly generate COUNT queries", ->
      @runScenario Thread,
        builder: (q) -> q.where({namespaceId: 'abcd'}).count()
        sql: "SELECT COUNT(*) as count FROM `Thread`  \
              WHERE `namespace_id` = 'abcd'  "

    it "should correctly generate LIMIT 1 queries for single items", ->
      @runScenario Thread,
        builder: (q) -> q.where({namespaceId: 'abcd'}).one()
        sql: "SELECT `Thread`.`data` FROM `Thread`  \
              WHERE `namespace_id` = 'abcd'  \
              ORDER BY `Thread`.`last_message_timestamp` DESC LIMIT 1"

    it "should correctly generate `contains` queries using JOINS", ->
      @runScenario Thread,
        builder: (q) -> q.where(Thread.attributes.tags.contains('inbox')).where({id: '1234'})
        sql: "SELECT `Thread`.`data` FROM `Thread` \
              INNER JOIN `Thread-Tag` AS `M1` ON `M1`.`id` = `Thread`.`id` \
              WHERE `M1`.`value` = 'inbox' AND `id` = '1234'  \
              ORDER BY `Thread`.`last_message_timestamp` DESC"

      @runScenario Thread,
        builder: (q) -> q.where([Thread.attributes.tags.contains('inbox'), Thread.attributes.tags.contains('unread')])
        sql: "SELECT `Thread`.`data` FROM `Thread` \
              INNER JOIN `Thread-Tag` AS `M1` ON `M1`.`id` = `Thread`.`id` \
              INNER JOIN `Thread-Tag` AS `M2` ON `M2`.`id` = `Thread`.`id` \
              WHERE `M1`.`value` = 'inbox' AND `M2`.`value` = 'unread'  \
              ORDER BY `Thread`.`last_message_timestamp` DESC"

    it "should correctly generate queries with the class's naturalSortOrder when one is available and no other orders are provided", ->
      @runScenario Thread,
        builder: (q) -> q.where({namespaceId: 'abcd'})
        sql: "SELECT `Thread`.`data` FROM `Thread`  \
              WHERE `namespace_id` = 'abcd'  \
              ORDER BY `Thread`.`last_message_timestamp` DESC"

      @runScenario Thread,
        builder: (q) -> q.where({namespaceId: 'abcd'}).order(Thread.attributes.lastMessageTimestamp.ascending())
        sql: "SELECT `Thread`.`data` FROM `Thread`  \
              WHERE `namespace_id` = 'abcd'  \
              ORDER BY `Thread`.`last_message_timestamp` ASC"

      @runScenario Namespace,
        builder: (q) -> q.where({id: 'abcd'})
        sql: "SELECT `Namespace`.`data` FROM `Namespace`  \
              WHERE `id` = 'abcd'  "

