using AddressValidation.DataLayer;
using AddressValidation.DataLayer.Entities;
using dataladder.Data;
using DataMatch.Project.Descriptors;
using DataMatch.Project.Helpers;
using log4net;
using SampleServiceNamespace;
using SdkInterface;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace AddressValidation.Backend
{
    public class RecordCleaner
    {
        protected readonly ILog Log = LogManager.GetLogger(nameof(RecordCleaner));

        private string _workingFolder;
        private List<string> _log;

        private EngineWrapper _engine;
        private List<ApiTransformation> _apiTransformations;
        private DataTable _holdingTable;

        private Steps _logSteps;
        private State _state;

        public Boolean? IsLastInsertedRecordUnique = null;

        public RecordCleaner(
            String settingsFilePath,
            String connectionString)
        {
            //Initializing
            AddToLog(Steps.None, State.Initializing);

            ApiSettings.Init(settingsFilePath);
            Log.Debug("Record cleaner initializing. Application settings loaded.");

            ContactDbManager.ConnectionString = connectionString;
            
            if (!CheckRegistration(out string regError))
                throw new Exception(regError);

            AddToLog(Steps.None, State.Waiting);

        }

        public RecordCleaner(String projectWithTransformationRules,
                             String projectWithMatchingRules,
                             String settingsFilePath,
                             String connectionString
                             )
        {
            //Initializing
            AddToLog(Steps.None, State.Initializing);

            ApiSettings.Init(settingsFilePath);
            Log.Debug("Record cleaner initializing. Application settings loaded.");

            ContactDbManager.ConnectionString = connectionString;

            dataladder.Matching.ApplicationSettings.ProjectsPath =
                    System.IO.Path.GetDirectoryName(projectWithTransformationRules);
            _workingFolder = System.IO.Path.Combine(ApiPaths.DataPath, "API");

            if (!CheckRegistration(out string regError))
                throw new Exception(regError);

            // Extract from project file an information about transformation rules
            ProjectSpec projectSpec = new ProjectSpec();
            ProjectSpecHelper.LoadProject(projectSpec, projectWithTransformationRules);
            var sources = projectSpec.DataSource.DataSources;
            
            var _transformations = sources[0].StandardizedInfo.Transformations;
            _apiTransformations = ApiTransformation.ConvertToTransformations(_transformations);

            Load(projectWithMatchingRules);
            AddToLog(Steps.None, State.Waiting);
        }

        public static Boolean ValidateProjects(
            String projectWithTransformationRules,
            String projectWithMatchingRules, out String message)
        {
            message = "";

            ProjectSpec projectSpec = new ProjectSpec();
            ProjectSpecHelper.LoadProject(projectSpec, projectWithTransformationRules);
            var sources = projectSpec.DataSource.DataSources;
            if (sources.Count == 0)
            {
                message += $"Incorrect project '{projectWithTransformationRules}'.{Environment.NewLine}" +
                    $"Please change project settings: TransformationProject{Environment.NewLine}";
            }

            ProjectSpec projectSpec2 = new ProjectSpec();
            ProjectSpecHelper.LoadProject(projectSpec2, projectWithTransformationRules);
            var sources2 = projectSpec.DataSource.DataSources;
            if (sources2.Count == 0)
            {
                message += $"Incorrect project '{projectWithMatchingRules}'.{Environment.NewLine}" +
                    $"Please change project settings: MatchingProject{Environment.NewLine}";
            }

            return String.IsNullOrEmpty(message);
        }

        private void Load(String projectWithMatchingRules)
        {
            // Load matching rules
            _engine = new EngineWrapper(
                System.IO.Path.GetFileNameWithoutExtension(projectWithMatchingRules),
                useCache: true);

            if (!string.IsNullOrEmpty(_engine.Errors))
            {
                throw new Exception(_engine.Errors);
            }
        }

        public bool CheckRegistration(out String error)
        {
            ApiRegistration registration = new ApiRegistration();
            DateTime expirationTime = registration.ExpirationDate;

            if (expirationTime < DateTime.Now)
            {
                error = $"expired: {expirationTime} < {DateTime.Now}";
                return false;
            }

            if (!registration.IsApi)
            {
                error = "not api";
                return false;
            }

            error = "";
            return true;
        }

        public bool InsertRecords(string[] values, string[] columnNames,
            out DataTable holdTable, out DataTable transTable,
            out DataTable matchTable, out DataTable masterTable,
            out DataTable suppresedTable, out string[] msgs)
        {
            bool res = false;
            bool verifiedSuccesful = false;
            matchTable = null;
            masterTable = null;
            suppresedTable = null;

            ResetLog();
            AddToLog(Steps.None, State.InsertingInHoldingTable);

            // Process input array - keep needed count of records
            List<string> variables = new List<string>();
            for (int i = 0; i < columnNames.Length; i++)
            {
                if (i < values.Length)
                    variables.Add(values[i]);
                else
                    variables.Add("");
            }

            // Save input data in holding table
            WriteToHoldingTable(columnNames.ToList(), variables);
            holdTable = GetHoldingTable();

            AddToLog(Steps.InputRecordIsAddedToHoldingTable,
                State.InputRecordTransforming, "Record is inserted in Holding table");

            // Make transformation. Cleansing input data
            ApiSource source = new ApiSource("Data Source 1", _workingFolder, values,
                columnNames);
            source.SetTransformations(_apiTransformations);
            //Log.Debug($"{dataladder.Matching.ApplicationSettings.GetSettingsAsText()}");
            source.RunTransformation();
            var resultTransformation = source.TransformedValues;
            
            // defining is address verification succesful
            if (resultTransformation.Rows.Count > 0)
            {
                if (resultTransformation.Columns.Contains("V Status"))
                {
                    string status = resultTransformation.Rows[0]["V Status"]?.ToString();
                    if (status.Equals("V"))
                        verifiedSuccesful = true;
                }
            }

            // remove unused columns
            //transTable = ContactInfoHelper.Normilize(resultTransformation, verifiedSuccesful);
            transTable = FilterColumns(resultTransformation);

            AddToLog(
                Steps.InputRecordTransformed |
                    (verifiedSuccesful ?
                            Steps.AddressVerifiedSuccesfuly :
                            Steps.AddressDoesntVerified),
                State.SearchingDuplicates, "Record is cleansed");

            string[] names = _engine.MatchingFieldNames;
            string[] mvar = new string[names.Length];
            if (transTable.Rows.Count > 0)
            {
                //extract from transformation results the information 
                //that will be used in matching
                DataRow row = transTable.Rows[0];
                for (int i = 0; i < names.Length; i++)
                {
                    if (transTable.Columns.Contains(names[i]))
                    {
                        mvar[i] = (row[names[i]] ?? "").ToString();
                    }
                    else
                    {
                        mvar[i] = "";
                    }
                }

                //find dulpicates
                InMemoryTable matchesIn = _engine.FindMatches(mvar, 10);
                if (matchesIn.RecordCount == 0)
                {
                    AddToLog(Steps.ItsUniqueRecord, State.InsertingInOutputTable, "Record is unique");
                    //unique
                    IsLastInsertedRecordUnique = true;
                    WriteToMasterTable(transTable, verifiedSuccesful);
                    _engine.AddToCacheTableRecord(transTable);
                    AddToLog(Steps.InsertedIntoMasterTable, State.Waiting, "Record is inserted in Master table");
                }
                else
                {
                    //duplicates exist
                    AddToLog(Steps.DupplicateFounded, State.InsertingInOutputTable,
                        $"Record has duplicates {matchesIn.RecordCount}");
                    IsLastInsertedRecordUnique = false;
                    WriteToSuppresedTable(transTable, verifiedSuccesful);
                    matchTable = ConvertOnDriveToDataTable(matchesIn);
                    AddToLog(Steps.InsertedIntoSuppresedTable, State.Waiting, "Record is inserted in Suppresed table");
                }

                masterTable = GetMasterTable(); // ConvertOnDriveToDataTable(_engine.GetUnitedTable());
                suppresedTable = GetSuppresedTable();

                res = true;
            }

            AddToLog(Steps.None, State.Waiting, "Finished");

            msgs = GetAndResetLog().ToArray();
            return res;
        }

        private DataTable FilterColumns(DataTable table)
        {
            string[] columns = {
                "FirstName", "Common Name", "LastName",
                "V Primary Address", "Address1", "Address2",
                "V Secondary Address", "V City", "City", "V State",
                "State", "V Zip Code", "Zip", "V Country", "Country",
                "Phone", "Email", "Custom1", "Custom2", "V Status" };

            DataTable res = new DataTable();
            foreach (var col in columns)
            {
                res.Columns.Add(col, typeof(string));
            }
            if (table.Rows.Count > 0)
            {
                var row = res.NewRow();
                for (int i = 0; i < table.Columns.Count; i++)
                {
                    var col = table.Columns[i].ColumnName;
                    if (columns.Contains(col))
                    {
                        row[col] = table.Rows[0][col];
                    }
                }
                res.Rows.Add(row);
                res.AcceptChanges();
            }
            return res;
        }

        public void InitHoldingTable(List<String> colNames)
        {
            _holdingTable = new DataTable("HoldingTable");
            for (Int32 i = 0; i < colNames.Count; i++)
            {
                _holdingTable.Columns.Add(colNames[i], typeof(String));
            }
        }

        /// <summary>
        /// Converts first row of DataTable to string array
        /// </summary>
        /// <param name="tbl"></param>
        /// <returns></returns>
        private string[] DataTableToStringArray(DataTable tbl)
        {
            List<string> res = new List<string>();
            if (tbl.Rows.Count > 0)
            {
                DataRow row = tbl.Rows[0];
                for (int j = 0; j < tbl.Columns.Count; j++)
                {
                    res.Add(row[j]?.ToString());
                }
            }
            return res.ToArray();
        }

        /// <summary> 
        /// Adds to History (holding) table the inpur from user.
        /// If table doesn't exist then create it
        /// </summary>
        /// <param name="transforms"></param>
        /// <param name="values"></param>
        private void WriteToHoldingTable(List<string> columns, List<string> values)
        {
            // if Holding Table doesn't exit then сreate it
            if (_holdingTable == null)
            {
                InitHoldingTable(columns);
            }

            // add row to the table
            var row = _holdingTable.NewRow();
            for (int i = 0; i < columns.Count; i++)
            {
                if (_holdingTable.Columns.Contains(columns[i]))
                {
                    row[columns[i]] = values[i];
                }
            }
            _holdingTable.Rows.Add(row);

        }

        public DataTable GetHoldingTable()
        {
            return _holdingTable;
        }

        private void WriteToSuppresedTable(DataTable dt, bool isVerified)
        {
            var ci = ContactInfoHelper.CreateContactInfo(dt, true, isVerified);
            ContactDbManager.AddContact(ci);
        }

        public DataTable GetSuppresedTable()
        {
            var list = ContactDbManager.GetContacts(true);
            return ContactInfoHelper.ListContactInfoToDataTable(list);
        }

        private void WriteToMasterTable(DataTable dt, bool isVerified)
        {
            var ci = ContactInfoHelper.CreateContactInfo(dt, false, isVerified);
            ContactDbManager.AddContact(ci);
        }

        public DataTable GetMasterTable()
        {
            var list = ContactDbManager.GetContacts(false);
            return ContactInfoHelper.ListContactInfoToDataTable(list);
        }

        public void Clear()
        {
            ClearHolding();
            ContactDbManager.InitMasterTable();
            //_engine.Dispose();
            //Load();
        }

        public void ClearHolding()
        {
            _holdingTable = null;
        }

        public string[] GetMatchingFields()
        {
            return _engine?.MatchingFieldNames;
        }

        private DataTable ConvertOnDriveToDataTable(InMemoryTable drive)
        {
            if (drive == null)
                return null;

            DataTable dt = new DataTable(drive.Name);
            for (int i = 0; i < drive.ColumnCount; i++)
            {
                dt.Columns.Add(drive.GetColumnName(i), typeof(string));
            }
            for (int i = 0; i < drive.RecordCount; i++)
            {
                DataRow row = dt.NewRow();
                for (int j = 0; j < drive.ColumnCount; j++)
                {
                    row[j] = drive.GetData(i, j);
                }
                dt.Rows.Add(row);
            }
            return dt;
        }

        public List<string> GetAndResetLog()
        {
            var res = _log;
            _log = null;
            return res;
        }

        public string GetAsJsonAndResetLog()
        {
            var res = "{ \"log\": [";
            foreach (var item in _log)
            {
                res += $"\"{item}\"";
            }
            res += "]}";
            _log = null;
            return res;
        }

        private void AddToLog(Steps step, State state, string msg = null)
        {
            if (msg != null)
            {
                if (_log == null)
                {
                    _log = new List<string>();
                }
                _log.Add(msg);
            }
            _state = state;
            _logSteps = _logSteps | step;
        }

        private void ResetLog()
        {
            _log = null;
            _logSteps = Steps.None;
            _state = State.Waiting;
        }
    }

    [Flags]
    public enum Steps
    {
        None = 0,
        InputRecordIsAddedToHoldingTable = 1,
        InputRecordTransformed = 2,
        AddressVerifiedSuccesfuly = 4,
        AddressDoesntVerified = 8,
        DupplicateFounded = 16,
        ItsUniqueRecord = 32,
        InsertedIntoMasterTable = 64,
        InsertedIntoSuppresedTable = 128
    }

    public enum State
    {
        Initializing,
        InsertingInHoldingTable,
        InputRecordTransforming,
        SearchingDuplicates,
        InsertingInOutputTable,
        Waiting
    }
}
