/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.metron.solr.integration.schema;

import com.google.common.collect.Iterables;
import com.google.common.io.Files;
import org.apache.metron.common.configuration.writer.WriterConfiguration;
import org.apache.metron.common.utils.JSONUtils;
import org.apache.metron.common.writer.BulkMessage;
import org.apache.metron.common.writer.BulkWriterResponse;
import org.apache.metron.solr.integration.components.SolrComponent;
import org.apache.metron.solr.writer.SolrWriter;
import org.apache.metron.stellar.common.utils.ConversionUtils;
import org.json.simple.JSONObject;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;

import static org.apache.metron.solr.SolrConstants.SOLR_ZOOKEEPER;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class SchemaValidationIntegrationTest {
  public static Iterable<String> getData(String sensor) throws IOException {
    return Iterables.filter(
            Files.readLines(new File("src/test/resources/example_data/" + sensor), Charset.defaultCharset()),
            s -> !s.startsWith("#") && s.length() > 0
    );
  }

  public static Map<String, Object> getGlobalConfig(String sensorType, SolrComponent component) {
    Map<String, Object> globalConfig = new HashMap<>();
    globalConfig.put(SOLR_ZOOKEEPER, component.getZookeeperUrl());
    return globalConfig;
  }

  public static SolrComponent createSolrComponent(String sensor) {
    return new SolrComponent.Builder().build();
  }

  @Test
  public void testError() throws Exception {
    test("error");
  }

  @Test
  public void testBro() throws Exception {
    test("bro");
  }

  @Test
  public void testSnort() throws Exception {
    test("snort");
  }

  @Test
  public void testYaf() throws Exception {
    test("yaf");
  }

  public String getGuid(Map<String, Object> m) {
    if(m.containsKey("guid")) {
      return (String)m.get("guid");
    }
    else {
      return (String) m.get("original_string");
    }
  }

  public void test(String sensorType) throws Exception {
    SolrComponent component = null;
    try {
      component = createSolrComponent(sensorType);
      component.start();
      component.addCollection(String.format("%s", sensorType), String.format("src/main/config/schema/%s", sensorType));
      Map<String, Object> globalConfig = getGlobalConfig(sensorType, component);

      List<BulkMessage<JSONObject>> messages = new ArrayList<>();
      Map<String, Map<String, Object>> index = new HashMap<>();
      int i = 0;
      for (String message : getData(sensorType)) {
        if (message.trim().length() > 0) {
          Map<String, Object> m = JSONUtils.INSTANCE.load(message.trim(), JSONUtils.MAP_SUPPLIER);
          String guid = getGuid(m);
          index.put(guid, m);
          messages.add(new BulkMessage<>(String.format("message%d", ++i), new JSONObject(m)));
        }
      }
      assertTrue(messages.size() > 0);

      SolrWriter solrWriter = new SolrWriter();

      WriterConfiguration writerConfig = new WriterConfiguration() {
        @Override
        public int getBatchSize(String sensorName) {
          return messages.size();
        }

        @Override
        public int getBatchTimeout(String sensorName) {
          return 0;
        }

        @Override
        public List<Integer> getAllConfiguredTimeouts() {
          return new ArrayList<>();
        }

        @Override
        public String getIndex(String sensorName) {
          return sensorType;
        }

        @Override
        public boolean isEnabled(String sensorName) {
          return true;
        }

        @Override
        public Map<String, Object> getSensorConfig(String sensorName) {
          return new HashMap<String, Object>() {{
            put("index", sensorType);
            put("batchSize", messages.size());
            put("enabled", true);
          }};
        }

        @Override
        public Map<String, Object> getGlobalConfig() {
          return globalConfig;
        }

        @Override
        public boolean isDefault(String sensorName) {
          return false;
        }

        @Override
        public String getFieldNameConverter(String sensorName) {
          return null;
        }
      };

      solrWriter.init(null, writerConfig);

      BulkWriterResponse response = solrWriter.write(sensorType, writerConfig, messages);
      assertTrue(response.getErrors().isEmpty());
      for (Map<String, Object> m : component.getAllIndexedDocs(sensorType)) {
        Map<String, Object> expected = index.get(getGuid(m));
        for (Map.Entry<String, Object> field : expected.entrySet()) {
          if (field.getValue() instanceof Collection && ((Collection) field.getValue()).size() == 0) {
            continue;
          }
          if(m.get(field.getKey()) instanceof Number) {
            Number n1 = ConversionUtils.convert(field.getValue(), Double.class);
            Number n2 = (Number)m.get(field.getKey());
            boolean isSame = Math.abs(n1.doubleValue() - n2.doubleValue()) < 1e-3;
            if(!isSame) {
              String s1 = "" + n1.doubleValue();
              String s2 = "" + n2.doubleValue();
              isSame = s1.startsWith(s2) || s2.startsWith(s1);
            }
            assertTrue(isSame, "Unable to validate " + field.getKey() + ": " + n1 + " != " + n2);
          }
          else {
            assertEquals("" + field.getValue(), "" + m.get(field.getKey()), "Unable to find " + field.getKey());
          }
        }
      }
    }
    finally {
      if(component != null) {
        component.stop();
      }
    }
  }

}
