# -*- coding: utf-8 -*-
import json

from collections import namedtuple
from summary import skip_rules_update
import testutil
import test_engine


class TestRules(test_engine.EngineTestCase):
  sample = testutil.parse_test_sample({
    "SCHEMA": [
      [1, "Inventory", [
        [2, "Label", "Text", False, "", "", ""],
        [3, "Stock", "Int", False, "", "", ""],
      ]],
    ],
    "DATA": {
      "Inventory": [
        ["id", "Label", "Stock"],
        [1, "A1", 0],
        [2, "A2", 2],
        [3, "A3", 5],
        # Duplicate
        [4, "A1", 10]
      ],
    }
  })

  # Helper for rules action
  def add_empty(self, col_id):
    return self.apply_user_action(['AddEmptyRule', "Inventory", 0, col_id])

  def field_add_empty(self, field_id):
    return self.apply_user_action(['AddEmptyRule', "Inventory", field_id, 0])

  def set_rule(self, col_id, rule_index, formula):
    rules = self.engine.docmodel.columns.table.get_record(col_id).rules
    rule = list(rules)[rule_index]
    return self.apply_user_action(['UpdateRecord', '_grist_Tables_column',
                                   rule.id, {"formula": formula}])

  def field_set_rule(self, field_id, rule_index, formula):
    rules = self.engine.docmodel.view_fields.table.get_record(field_id).rules
    rule = list(rules)[rule_index]
    return self.apply_user_action(['UpdateRecord', '_grist_Tables_column',
                                   rule.id, {"formula": formula}])

  def remove_rule(self, col_id, rule_index):
    rules = self.engine.docmodel.columns.table.get_record(col_id).rules
    rule = list(rules)[rule_index]
    return self.apply_user_action(['RemoveColumn', 'Inventory', rule.colId])

  def field_remove_rule(self, field_id, rule_index):
    rules = self.engine.docmodel.view_fields.table.get_record(field_id).rules
    rule = list(rules)[rule_index]
    return self.apply_user_action(['RemoveColumn', 'Inventory', rule.colId])

  def test_summary_updates(self):
    Col = namedtuple('Col', 'widgetOptions')
    col = Col(None)
    # Should remove rules from update
    self.assertEqual({}, skip_rules_update(col, {'rules': [15]}))
    # Should leave col_updates untouched when there are no rules.
    col_updates = {'type': 'Int'}
    self.assertEqual(col_updates, skip_rules_update(col, col_updates))

    # Should return same dict when not updating ruleOptions
    col_updates = {'widgetOptions': '{"color": "red"}'}
    self.assertEqual(col_updates, skip_rules_update(col, col_updates))
    col = Col('{"color": "red"}')
    self.assertEqual(col_updates, skip_rules_update(col, col_updates))

    # Should remove ruleOptions from update
    col_updates = {'widgetOptions': '{"rulesOptions": [{"color": "black"}], "color": "blue"}'}
    self.assertEqual({'widgetOptions': '{"color": "blue"}'},
                         skip_rules_update(col, col_updates))
    col_updates = {'widgetOptions': '{"rulesOptions": [], "color": "blue"}'}
    self.assertEqual({'widgetOptions': '{"color": "blue"}'},
                         skip_rules_update(col, col_updates))

    # Should preserve original ruleOptions
    col = Col('{"rulesOptions": [{"color":"red"}], "color": "blue"}')
    col_updates = {'widgetOptions': '{"rulesOptions": [{"color": "black"}], "color": "red"}'}
    updated = skip_rules_update(col, col_updates)
    self.assertEqual({"rulesOptions": [{"color": "red"}], "color": "red"},
                         json.loads(updated.get('widgetOptions')))
    col_updates = {'widgetOptions': '{"color": "red"}'}
    updated = skip_rules_update(col, col_updates)
    self.assertEqual({"rulesOptions": [{"color": "red"}], "color": "red"},
                         json.loads(updated.get('widgetOptions')))


  def test_simple_rules(self):
    self.load_sample(self.sample)
    # Mark all records with Stock = 0
    out_actions = self.add_empty(3)
    self.assertPartialOutActions(out_actions, {"stored": [
      ["AddColumn", "Inventory", "gristHelper_ConditionalRule",
       {"formula": "", "isFormula": True, "type": "Any"}],
      ["AddRecord", "_grist_Tables_column", 4,
       {"colId": "gristHelper_ConditionalRule", "formula": "", "isFormula": True,
        "label": "gristHelper_ConditionalRule", "parentId": 1, "parentPos": 3.0,
        "type": "Any",
        "widgetOptions": ""}],
      ["UpdateRecord", "_grist_Tables_column", 3, {"rules": ["L", 4]}],
    ]})
    out_actions = self.set_rule(3, 0, "$Stock == 0")
    self.assertPartialOutActions(out_actions, {"stored": [
      ["ModifyColumn", "Inventory", "gristHelper_ConditionalRule",
       {"formula": "$Stock == 0"}],
      ["UpdateRecord", "_grist_Tables_column", 4, {"formula": "$Stock == 0"}],
      ["BulkUpdateRecord", "Inventory", [1, 2, 3, 4],
       {"gristHelper_ConditionalRule": [True, False, False, False]}],
    ]})

    # Replace this rule with another rule to mark Stock = 2
    out_actions = self.set_rule(3, 0, "$Stock == 2")
    self.assertPartialOutActions(out_actions, {"stored": [
      ["ModifyColumn", "Inventory", "gristHelper_ConditionalRule",
       {"formula": "$Stock == 2"}],
      ["UpdateRecord", "_grist_Tables_column", 4, {"formula": "$Stock == 2"}],
      ["BulkUpdateRecord", "Inventory", [1, 2],
       {"gristHelper_ConditionalRule": [False, True]}],
    ]})

    # Add another rule Stock = 10
    out_actions = self.add_empty(3)
    self.assertPartialOutActions(out_actions, {"stored": [
      ["AddColumn", "Inventory", "gristHelper_ConditionalRule2",
       {"formula": "", "isFormula": True, "type": "Any"}],
      ["AddRecord", "_grist_Tables_column", 5,
       {"colId": "gristHelper_ConditionalRule2", "formula": "", "isFormula": True,
        "label": "gristHelper_ConditionalRule2", "parentId": 1, "parentPos": 4.0,
        "type": "Any",
        "widgetOptions": ""}],
      ["UpdateRecord", "_grist_Tables_column", 3, {"rules": ["L", 4, 5]}],
    ]})
    out_actions = self.set_rule(3, 1, "$Stock == 10")
    self.assertPartialOutActions(out_actions, {"stored": [
      ["ModifyColumn", "Inventory", "gristHelper_ConditionalRule2",
       {"formula": "$Stock == 10"}],
      ["UpdateRecord", "_grist_Tables_column", 5, {"formula": "$Stock == 10"}],
      ["BulkUpdateRecord", "Inventory", [1, 2, 3, 4],
       {"gristHelper_ConditionalRule2": [False, False, False, True]}],
    ]})

    # Remove the last rule
    out_actions = self.remove_rule(3, 1)
    self.assertPartialOutActions(out_actions, {"stored": [
      ["RemoveRecord", "_grist_Tables_column", 5],
      ["UpdateRecord", "_grist_Tables_column", 3, {"rules": ["L", 4]}],
      ["RemoveColumn", "Inventory", "gristHelper_ConditionalRule2"]
    ]})

    # Remove last rule
    out_actions = self.remove_rule(3, 0)
    self.assertPartialOutActions(out_actions, {"stored": [
      ["RemoveRecord", "_grist_Tables_column", 4],
      ["UpdateRecord", "_grist_Tables_column", 3, {"rules": None}],
      ["RemoveColumn", "Inventory", "gristHelper_ConditionalRule"]
    ]})

  def test_duplicates(self):
    self.load_sample(self.sample)

    # Create rule that marks duplicate values
    formula = "len(Inventory.lookupRecords(Label=$Label)) > 1"

    # First add rule on stock column, to test naming - second rule column should have 2 as a suffix
    self.add_empty(3)
    self.set_rule(3, 0, "$Stock == 0")
    # Now highlight duplicates on labels
    self.add_empty(2)
    out_actions = self.set_rule(2, 0, formula)
    self.assertPartialOutActions(out_actions, {"stored": [
      ["ModifyColumn", "Inventory", "gristHelper_ConditionalRule2",
       {"formula": "len(Inventory.lookupRecords(Label=$Label)) > 1"}],
      ["UpdateRecord", "_grist_Tables_column", 5,
       {"formula": "len(Inventory.lookupRecords(Label=$Label)) > 1"}],
      ["BulkUpdateRecord", "Inventory", [1, 2, 3, 4],
       {"gristHelper_ConditionalRule2": [True, False, False, True]}]
    ]})

  def test_column_removal(self):
    # Test that rules are removed with a column.

    self.load_sample(self.sample)
    self.add_empty(3)
    self.set_rule(3, 0, "$Stock == 0")
    before = self.engine.docmodel.columns.lookupOne(colId='gristHelper_ConditionalRule')
    self.assertNotEqual(before, 0)
    out_actions = self.apply_user_action(['RemoveColumn', 'Inventory', 'Stock'])
    self.assertPartialOutActions(out_actions, {"stored": [
      ["BulkRemoveRecord", "_grist_Tables_column", [3, 4]],
      ["RemoveColumn", "Inventory", "Stock"],
      ["RemoveColumn", "Inventory", "gristHelper_ConditionalRule"],
    ]})

  def test_column_removal_for_a_field(self):
    # Test that rules are removed with a column when attached to a field.

    self.load_sample(self.sample)
    self.apply_user_action(['CreateViewSection', 1, 0, 'record', None, None])
    self.field_add_empty(2)
    self.field_set_rule(2, 0, "$Stock == 0")
    before = self.engine.docmodel.columns.lookupOne(colId='gristHelper_ConditionalRule')
    self.assertNotEqual(before, 0)
    out_actions = self.apply_user_action(['RemoveColumn', 'Inventory', 'Stock'])
    self.assertPartialOutActions(out_actions, {"stored": [
      ["RemoveRecord", "_grist_Views_section_field", 2],
      ["BulkRemoveRecord", "_grist_Tables_column", [3, 4]],
      ["RemoveColumn", "Inventory", "Stock"],
      ["RemoveColumn", "Inventory", "gristHelper_ConditionalRule"],
    ]})

  def test_field_removal(self):
    # Test that rules are removed with a field.

    self.load_sample(self.sample)
    self.apply_user_action(['CreateViewSection', 1, 0, 'record', None, None])
    self.field_add_empty(2)
    self.field_set_rule(2, 0, "$Stock == 0")
    rule_id = self.engine.docmodel.columns.lookupOne(colId='gristHelper_ConditionalRule').id
    self.assertNotEqual(rule_id, 0)
    out_actions = self.apply_user_action(['RemoveRecord', '_grist_Views_section_field', 2])
    self.assertPartialOutActions(out_actions, {"stored": [
      ["RemoveRecord", "_grist_Views_section_field", 2],
      ["RemoveRecord", "_grist_Tables_column", rule_id],
      ["RemoveColumn", "Inventory", "gristHelper_ConditionalRule"]
    ]})