using System.Collections.Generic;
using System.Linq;
using AyaNova.Biz;
using Newtonsoft.Json.Linq;
using AyaNova.Api.ControllerHelpers;
using Microsoft.Extensions.Logging;
using AyaNova.Models;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using EnumsNET;

namespace AyaNova.DataList
{
    internal static class DataListFetcher
    {
        internal static async Task<ApiDataListResponse> GetResponseAsync(string DataListKey, AyContext ct, ListOptions listOptions, AuthorizationRoles UserRoles, ILogger log)
        {

            var DataList = DataListFactory.GetAyaDataList(DataListKey);

            //was the name not found as a list?
            if (DataList == null)
            {
                throw new System.ArgumentOutOfRangeException($"DataList \"{DataListKey}\" specified does not exist");
            }

            //check rights

            if (!UserRoles.HasAnyFlags(DataList.AllowedRoles))
                throw new System.UnauthorizedAccessException("User roles insufficient for this datalist");

            //do we need to default the listView?
            if (string.IsNullOrWhiteSpace(listOptions.ListView))
            {
                listOptions.ListView = DataList.DefaultListView;
            }

            //parse the list view     
            var ListViewArray = JArray.Parse(listOptions.ListView);


            //Get the field key names in a list from the listview
            List<string> ListViewFieldList = DataList.GetFieldListFromListView(ListViewArray);

            //BUILD THE QUERY                     
            //SELECT FRAGMENT COLUMNS FROM TEMPLATE               
            var SelectBuild = DataListSqlSelectBuilder.Build(DataList.FieldDefinitions, ListViewFieldList);

            //FROM CLAUSE             
            var qFrom = DataList.SQLFrom;

            var qWhere = string.Empty;
            var qOrderBy = string.Empty;

            //WHERE CLAUSE - FILTER            
            qWhere = DataListSqlFilterCriteriaBuilder.DataFilterToSQLCriteria(DataList.FieldDefinitions, ListViewArray);

            //ORDER BY CLAUSE - SORT 
            //BUILD ORDER BY 
            qOrderBy = DataListSqlFilterOrderByBuilder.DataFilterToSQLOrderBy(DataList.FieldDefinitions, ListViewArray);

            //LIMIT AND OFFSET CLAUSE - PAGING
            listOptions.Offset = listOptions.Offset ?? ListOptions.DefaultOffset;
            listOptions.Limit = listOptions.Limit ?? ListOptions.DefaultLimit;
            var qLimitOffset = $"LIMIT {listOptions.Limit} OFFSET {listOptions.Offset}";

            //PUT IT ALL TOGETHER
            string qDataQuery = string.Empty;
            string qTotalRecordsQuery = string.Empty;

            qDataQuery = $"{SelectBuild.Select} {qFrom} {qWhere} {qOrderBy} {qLimitOffset}".Replace("  ", " ");
            qTotalRecordsQuery = $"SELECT COUNT(*) {qFrom} {qWhere}".Replace("  ", " ");

            //RETURN OBJECTS                  
            int returnRowColumnCount = ListViewFieldList.Count();
            List<List<AyaFieldData>> rows = new List<List<AyaFieldData>>();
            long totalRecordCount = 0;

            //QUERY THE DB
            using (var command = ct.Database.GetDbConnection().CreateCommand())
            {
                await ct.Database.OpenConnectionAsync();

                //GET DATA RETURN ROWS
                command.CommandText = qDataQuery;
                try
                {
                    using (var dr = await command.ExecuteReaderAsync())
                    {
                        while (dr.Read())
                        {
                            List<AyaFieldData> row = new List<AyaFieldData>(returnRowColumnCount);


                            //INSERT REMAINING FIELDS FROM TEMPLATE INTO THE RETURN ROWS LIST
                            foreach (string TemplateField in ListViewFieldList)
                            {

                                //get the AyaObjectFieldDefinition
                                AyaDataListFieldDefinition f = DataList.FieldDefinitions.FirstOrDefault(z => z.FieldKey == TemplateField);
                                if (f.IsCustomField)
                                {

                                    AyaFieldData AyaField = new AyaFieldData();
                                    var cust = dr.GetString(SelectBuild.map[f.GetSqlValueColumnName()]);
                                    if (!string.IsNullOrWhiteSpace(cust))
                                    {
                                        JObject j = JObject.Parse(cust);
                                        //convert field name to cust name then get value
                                        var InternalCustomFieldName = AyaFormFieldDefinitions.TranslateLTCustomFieldToInternalCustomFieldName(TemplateField);
                                        //Sometimes a custom field is specified but doesn't exist in the collection so don't assume it's there
                                        // AyaField.v = j[InternalCustomFieldName].Value<object>();
                                        JToken o = j[InternalCustomFieldName];
                                        if (o != null)
                                            AyaField.v = o.Value<object>();
                                        else
                                            AyaField.v = null;

                                        row.Add(AyaField);
                                    }

                                    /*
                                    TODO: Custom field handling
                                    GetName works just not with multipart identifiers
                                    I could force naming by making all fields and AS, or
                                    I could map the ordinal when generating the Select fields so that I have a map to refer to here
                                    mapping in advance actually makes a lot of sense, then no more of this fragility of going by pointer index and hoping for the best
                                    it would just be premapped out.

                                    dr.GetOrdinal(f.SqlValueColumnName)
    'dr.GetOrdinal(f.SqlValueColumnName)' threw an exception of type 'System.IndexOutOfRangeException'
    f.SqlValueColumnName
    "awidget.customfields"
    dr.GetName(nCurrentColumnPointer)
    "customfields"
    dr.GetOrdinal("customfields");
    5

                                    */
                                }
                                else
                                {
                                    AyaFieldData AyaField = new AyaFieldData();
                                    AyaField.v = dr.GetValue(SelectBuild.map[f.GetSqlValueColumnName()]);

                                    if (f.IsRowId)
                                    {
                                        AyaField.rid = true;
                                    }
                                    else
                                    {
                                        AyaField.rid = null;
                                    }

                                    if (f.SqlIdColumnName != null)
                                    {
                                        var ordinal = SelectBuild.map[f.SqlIdColumnName];
                                        if (!await dr.IsDBNullAsync(ordinal))
                                            AyaField.i = dr.GetInt64(ordinal);

                                    }
                                    row.Add(AyaField);
                                }
                            }
                            rows.Add(row);
                        }
                    }

                    //GET TOTAL RECORD COUNT
                    command.CommandText = qTotalRecordsQuery;
                    using (var dr = await command.ExecuteReaderAsync())
                    {
                        if (dr.Read())
                        {
                            totalRecordCount = dr.GetInt64(0);
                        }
                    }
                }
                catch (Npgsql.PostgresException e)
                {
                    //log out the exception and the query
                    log.LogInformation("DataList query failed unexpectedly.  Data Query was:");
                    log.LogInformation(qDataQuery);
                    log.LogInformation("Count Query was:");
                    log.LogInformation(qTotalRecordsQuery);
                    log.LogInformation(e, "DB Exception");
                    throw new System.Exception("DataListFetcher - Query failed see log");

                }
            }



            //BUILD THE COLUMNS RETURN PROPERTY JSON FRAGMENT          
            Newtonsoft.Json.Linq.JArray ColumnsJSON = null;

            ColumnsJSON = DataList.GenerateListColumnsJSONFromListView(ListViewArray);

            return new ApiDataListResponse(rows, totalRecordCount, ColumnsJSON);

        }


        //Get a list of id's for reporting
        internal static async Task<long[]> GetIdListResponseAsync(string dataListKey, string listView, AyContext ct, AuthorizationRoles userRoles, ILogger log)
        {

            var DataList = DataListFactory.GetAyaDataList(dataListKey);

            //was the name not found as a list?
            if (DataList == null)
            {
                throw new System.ArgumentOutOfRangeException($"DataList \"{dataListKey}\" specified does not exist");
            }

            //check rights

            if (!userRoles.HasAnyFlags(DataList.AllowedRoles))
                throw new System.UnauthorizedAccessException("User roles insufficient for this datalist");

            //do we need to default the listView?
            if (string.IsNullOrWhiteSpace(listView))
            {
                listView = DataList.DefaultListView;
            }

            //parse the list view     
            var ListViewArray = JArray.Parse(listView);


            //Get the field key names in a list from the listview
            List<string> ListViewFieldList = DataList.GetFieldListFromListView(ListViewArray);

            //BUILD THE QUERY                     
            //SELECT FRAGMENT COLUMNS FROM TEMPLATE               
            var SelectBuild = DataListSqlSelectBuilder.BuildForReportIdListOnly(DataList.FieldDefinitions, ListViewFieldList);

            //FROM CLAUSE             
            var qFrom = DataList.SQLFrom;

            var qWhere = string.Empty;
            var qOrderBy = string.Empty;

            //WHERE CLAUSE - FILTER            
            qWhere = DataListSqlFilterCriteriaBuilder.DataFilterToSQLCriteria(DataList.FieldDefinitions, ListViewArray);

            //ORDER BY CLAUSE - SORT 
            //BUILD ORDER BY 
            qOrderBy = DataListSqlFilterOrderByBuilder.DataFilterToSQLOrderBy(DataList.FieldDefinitions, ListViewArray);


            //PUT IT ALL TOGETHER
            string qDataQuery = string.Empty;

            qDataQuery = $"{SelectBuild.Select} {qFrom} {qWhere} {qOrderBy} ".Replace("  ", " ");

            //RETURN OBJECTS                  
            var retList = new List<long>();

            //QUERY THE DB
            using (var command = ct.Database.GetDbConnection().CreateCommand())
            {
                await ct.Database.OpenConnectionAsync();

                //GET DATA RETURN ROWS
                command.CommandText = qDataQuery;
                try
                {
                    using (var dr = await command.ExecuteReaderAsync())
                    {
                        while (dr.Read())
                        {

                            //  retList.Add(dr.GetInt64(0));
                            // //INSERT REMAINING FIELDS FROM TEMPLATE INTO THE RETURN ROWS LIST
                            foreach (string TemplateField in ListViewFieldList)
                            {

                                //get the AyaObjectFieldDefinition
                                AyaDataListFieldDefinition f = DataList.FieldDefinitions.FirstOrDefault(z => z.FieldKey == TemplateField);


                                // AyaFieldData AyaField = new AyaFieldData();
                                // AyaField.v = dr.GetValue(SelectBuild.map[f.GetSqlValueColumnName()]);

                                // if (f.IsRowId)
                                // {
                                //     AyaField.rid = true;
                                // }
                                // else
                                // {
                                //     AyaField.rid = null;
                                // }

                                if (f.IsRowId)
                                {
                                    var ordinal = SelectBuild.map[f.SqlIdColumnName];
                                    if (!await dr.IsDBNullAsync(ordinal))
                                        retList.Add(dr.GetInt64(ordinal));

                                }
                            }
                        }
                    }
                }
                catch (Npgsql.PostgresException e)
                {
                    //log out the exception and the query
                    log.LogInformation("DataListFetcher:GetIdListResponseAsync query failed unexpectedly.  Data Query was:");
                    log.LogInformation(qDataQuery);

                    log.LogInformation(e, "DB Exception");
                    throw new System.Exception("DataListFetcher:GetIdListResponseAsync - Query failed see log");

                }
            }

            //retList.Reverse();
            return retList.ToArray();

        }


    }//eoc
}//eons