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

namespace AyaNova.DataList
{
    internal static class DataListFetcher
    {
        internal static async Task<ApiPagedResponse> GetResponseAsync(string DataListKey, AyContext ct, IUrlHelper Url,
        string routeName, ListOptions listOptions, long UserId, AuthorizationRoles UserRoles)
        {

            // var AyaObjectFields = AyaObjectFieldDefinitions.AyaObjectFields(AyaObjectFieldDefinitions.TEST_WIDGET_USER_EMAIL_ADDRESS_LIST_KEY);
            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 (listOptions.Mini)
            {
                if (!UserRoles.HasAnyFlags(DataList.MiniListAllowedRoles))
                    throw new System.UnauthorizedAccessException("User roles insufficient for this mini format datalist");
            }
            else
            {
                if (!UserRoles.HasAnyFlags(DataList.FullListAllowedRoles))
                    throw new System.UnauthorizedAccessException("User roles insufficient for this full format datalist");
            }

            //FETCH DATALISTTEMPLATE HERE OR USE DEFAULT IF FAULTY OR NOT FOUND
            //start with default
            string JSONDataListTemplate = DataList.DefaultDataListDisplayTemplate;

            var CustomDataListTemplate = await ct.DataListTemplate.FirstOrDefaultAsync(x => x.DataListKey == listOptions.DataListKey);
            //Null is expected unless user has created a custom one
            if (CustomDataListTemplate != null)
            {
                //Make sure it's valid, if not then don't use it
                if (DataList.ValidateTemplate(CustomDataListTemplate.Template))
                    JSONDataListTemplate = CustomDataListTemplate.Template;
            }



            //PARSE THE TEMPLATE INTO A STRING ARRAY
            //SO WE KNOW WHICH FIELDS TO RETURN FROM QUERY
            var jtemplate = JObject.Parse(JSONDataListTemplate);

            //convert to strings array (https://stackoverflow.com/a/33836599/8939)
            string[] templateFieldList;
            if (listOptions.Mini)
            {
                templateFieldList = ((JArray)jtemplate["mini"]).ToObject<string[]>();
            }
            else
            {
                templateFieldList = ((JArray)jtemplate["full"]).ToObject<string[]>();
            }

            //BUILD THE QUERY                     
            //SELECT FRAGMENT COLUMNS FROM TEMPLATE               
            var qSelectColumns = DataListSqlSelectBuilder.Build(DataList.FieldDefinitions, JSONDataListTemplate, listOptions.Mini);

            //FROM CLAUSE             
            var qFrom = DataList.SQLFrom;

            //FILTERED?
            DataListFilter TheFilter = null;
            var qWhere = string.Empty;
            var qOrderBy = string.Empty;
            if (listOptions.DataFilterId > 0)
            {
                TheFilter = await ct.DataListFilter.FirstOrDefaultAsync(x => x.Id == listOptions.DataFilterId);
                //WHERE CLAUSE - FILTER            
                qWhere = DataListSqlFilterCriteriaBuilder.DataFilterToSQLCriteria(DataList.FieldDefinitions, TheFilter, DataList.FieldDefinitions, UserId);
                //ORDER BY CLAUSE - SORT 
                //BUILD ORDER BY AND APPEND IT
                qOrderBy = DataListSqlFilterOrderByBuilder.DataFilterToSQLOrderBy(DataList.FieldDefinitions, TheFilter);
            }

            //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;
            if (TheFilter != null)
            {
                qDataQuery = $"{qSelectColumns} {qFrom} {qWhere} {qOrderBy} {qLimitOffset}";
                qTotalRecordsQuery = $"SELECT COUNT(*) {qFrom} {qWhere}";
            }
            else
            {
                qDataQuery = $"{qSelectColumns} {qFrom} {qLimitOffset}";
                qTotalRecordsQuery = $"SELECT COUNT(*) {qFrom}";
            }



            //RETURN OBJECTS                  
            int returnRowColumnCount = templateFieldList.Count() + 1;//Templates don't have the DF column in them but we need it and it's in the query so plus one
            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;
                using (var dr = await command.ExecuteReaderAsync())
                {
                    while (dr.Read())
                    {
                        List<AyaFieldData> row = new List<AyaFieldData>(returnRowColumnCount);

                        //PROCESS THE DF DEFAULT FIRST COLUMN
                        //first column is always the underlying id value of the default record to open for this row in the client ui
                        if (!dr.IsDBNull(0))
                        {
                            row.Add(new AyaFieldData() { v = dr.GetInt64(0) });
                        }
                        else
                        {
#if (DEBUG)
                            throw new System.ArgumentNullException($"DEV ERROR in DataListFetcher.cs: fetching df column for {DataListKey} df value is null, expecting long int record value");
#endif
                        }

                        //GetOrdinal by name is flakey in npgsql so just going by field definition and ordinal numerically
                        int nCurrentColumnPointer = 1;//start at 1 

                        //INSERT REMAINING FIELDS FROM TEMPLATE INTO THE RETURN ROWS LIST
                        foreach (string TemplateField in templateFieldList)
                        {
                            //get the AyaObjectFieldDefinition
                            AyaDataListFieldDefinition f = DataList.FieldDefinitions.FirstOrDefault(x => x.FieldKey == TemplateField);

                            AyaFieldData AyaField = new AyaFieldData();
                            AyaField.v = dr.GetValue(nCurrentColumnPointer);
                            nCurrentColumnPointer++;

                            if (f.SqlIdColumnName != null)//skip over df column id, it's not there
                            {
                                if (!await dr.IsDBNullAsync(nCurrentColumnPointer))
                                    AyaField.i = dr.GetInt64(nCurrentColumnPointer);

                                nCurrentColumnPointer++;
                            }
                            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);
                    }
                }
            }

            //BUILD THE PAGING LINKS PORTION
            //var pageLinks = new PaginationLinkBuilder(Url, routeName, null, listOptions, totalRecordCount).PagingLinksObject();
            //http://localhost:7575/api/v8/DataList/List?Offset=2&Limit=3&DataFilterId=2&Mini=true&DataListKey=TestWidgetDataList

            object routeValues = null;
            //no data filter?
            if (listOptions.DataFilterId == 0)
            {
                if (listOptions.Mini)
                {
                    routeValues = new { DataListKey = DataListKey, Mini = listOptions.Mini };
                }
                else
                {
                    routeValues = new { DataListKey = DataListKey };
                }
            }
            else
            {
                if (listOptions.Mini)
                {
                    routeValues = new { DataListKey = DataListKey, DataFilterId = listOptions.DataFilterId, Mini = listOptions.Mini };
                }
                else
                {
                    routeValues = new { DataListKey = DataListKey, DataFilterId = listOptions.DataFilterId };
                }
            }



            var pageLinks = new PaginationLinkBuilder(Url, routeName, routeValues, listOptions, totalRecordCount).PagingLinksObject();

            //BUILD THE COLUMNS RETURN PROPERTY JSON FRAGMENT          
            Newtonsoft.Json.Linq.JArray ColumnsJSON = null;
            if (listOptions.Mini)
            {
                ColumnsJSON = DataList.GenerateMINIListColumnsJSON();
            }
            else
            {
                ColumnsJSON = DataList.GenerateListColumnsJSONFromTemplate(JSONDataListTemplate);
            }


            ApiPagedResponse pr = new ApiPagedResponse(rows, pageLinks, ColumnsJSON);
            return pr;
        }


    }//eoc
}//eons