using Sockeye.Models;
using System.Linq;
using Newtonsoft.Json.Linq;

namespace Sockeye.Biz
{
    //VALIDATE **USER DEFINED** (not stock) REQUIRED FIELDS THAT ARE NOT CUSTOM
    //(fields that are stock required are validated on their own not here)

    internal static class RequiredFieldsValidator
    {
        internal static void Validate(BizObject biz, FormCustom formCustom, object proposedObject)
        {
            //No form custom = no template to check against so nothing to do
            if (formCustom == null || string.IsNullOrWhiteSpace(formCustom.Template))
                return;
            var FormTemplate = JArray.Parse(formCustom.Template);
            var FormFields = Biz.FormFieldOptionalCustomizableReference.FormFieldReferenceList(formCustom.FormKey);

            foreach (JObject jo in FormTemplate)
            {
                if (jo["required"].Value<bool>() == true)
                {
                    //First get the LT key
                    var FldLtKey = jo["fld"].Value<string>();
                    //  - e.g.: {template:[{fld:"ltkeyfieldname",hide:"true/false",required:"true/false", type:"bool"},{fld:"ltkeyfieldname",hide:"true/false",required:"true/false", type:"text"]}

                    //get the FormField object
                    FormField FF = FormFields.Where(z => z.FieldKey == FldLtKey).Single();

                    //get the type and because of quote and pm subsections "TKeySection" property being named "WorkOrder" in the formFieldReference  due to lack of separate
                    //translations for quote and pm subitems it's necessary to adjust the name here first before matching
                    var proposedObjectType = proposedObject.GetType().ToString().Replace("Sockeye.Models.", "").Replace("QuoteItem", "WorkOrderItem").Replace("PMItem", "WorkOrderItem");

                    //hacky last minute work around but workorder, quote and pm header objects have no tkeysection normally which can cause interference here with duplicate fields i.e. Tags in subsections
                    //as they will have the header rule applied if we just leave the tkeysection as null so for here we workaround that by creating a temporary tkeysection
                    if (FF.TKeySection == null)
                    {
                        switch (proposedObjectType)
                        {
                            case "WorkOrder":
                                FF.TKeySection = proposedObjectType;
                                break;
                            case "Quote":
                                FF.TKeySection = proposedObjectType;
                                break;
                            case "PM":
                                FF.TKeySection = proposedObjectType;
                                break;
                        }
                    }

                    //don't validate custom fields, just skip them, make sure if it's sectional it matches the section of the object type (workorder sub sections)
                    if (!FF.IsCustomField && (FF.TKeySection == null || proposedObjectType == FF.TKeySection))
                    {

                        //bugbug: if section is workorderitem and field is tags but there is a tags required in workorder htat has NOT tkeysection it applies that rule


                        //Now get the actual property name from the available fields using the lt key
                        string RequiredPropertyName = FF.FieldKey;
                        //there might be a more specific model property due to being a workorder sub item duplicate such as WorkOrderItemTags vs WorkOrderTags
                        if (FF.ModelProperty != null)
                            RequiredPropertyName = FF.ModelProperty;

                        //Is it an indexed collection field?
                        if (RequiredPropertyName.Contains("."))
                        {
                            /*
                                flag errors to include parent    e.g in PO item validation error field name could be "Items[3].QuantityReceived" to indicate poitems collection 4th row has error in qtyreceived                                                       
                                Note: collections are logically never more than 3 deep so for example the deepest would be workorder granchildren: e.g. Workorder.Items.Parts and most
                                are two deep however like Po.PoItems.field
                                for purposes of rule checking they would be flagged by their immediate parent "Items[3].QuantityReceived" for po or for workorder it could be
                                a required UPC field in Workorder.Items.Parts.UPC would result in a target error return of "Items[2].Parts[3].UPC" would be valid
                            */
                            var FieldKeyParts = RequiredPropertyName.Split('.');
                            if (FieldKeyParts.Length == 2)
                            {
                                //parent collection -> child field
                                //target name like "Items.FieldName"
                                var parentCollection = proposedObject.GetType().GetProperty(FieldKeyParts[0]).GetValue(proposedObject, null);
                                int index = 0;
                                foreach (object ChildObject in (parentCollection as System.Collections.IEnumerable))
                                {
                                    var fieldValue = ChildObject.GetType().GetProperty(FieldKeyParts[1]).GetValue(ChildObject, null);
                                    if (fieldValue == null || string.IsNullOrWhiteSpace(fieldValue.ToString()))
                                        biz.AddError(ApiErrorCode.VALIDATION_CUSTOM_REQUIRED_EMPTY, $"{FieldKeyParts[0]}[{index}].{FieldKeyParts[1]}");
                                    index++;
                                }
                            }
                            else if (FieldKeyParts.Length == 3)
                            {
                                //grandparent collection -> Parent collection -> Child field
                                //target name like "WorkOrderItems.WorkOrderItemParts.UPC
                                var GrandParentCollection = proposedObject.GetType().GetProperty(FieldKeyParts[0]).GetValue(proposedObject, null);
                                int GrandParentIndex = 0;
                                foreach (object GrandParentObject in (GrandParentCollection as System.Collections.IEnumerable))
                                {
                                    var ParentCollection = GrandParentObject.GetType().GetProperty(FieldKeyParts[1]).GetValue(GrandParentObject, null);
                                    int ParentIndex = 0;
                                    foreach (object ChildObject in (ParentCollection as System.Collections.IEnumerable))
                                    {
                                        var fieldValue = ChildObject.GetType().GetProperty(FieldKeyParts[1]).GetValue(ChildObject, null);
                                        if (fieldValue == null || string.IsNullOrWhiteSpace(fieldValue.ToString()))
                                            biz.AddError(ApiErrorCode.VALIDATION_CUSTOM_REQUIRED_EMPTY, $"{FieldKeyParts[0]}[{GrandParentIndex}].{FieldKeyParts[1]}[{ParentIndex}].{FieldKeyParts[2]}");
                                        ParentIndex++;
                                    }
                                    GrandParentIndex++;
                                }
                            }
                        }
                        else
                        {
                            //It's a simple property on the main object 
                            //use reflection to get the underlying value from the proposed object to be saved
                            //issue the error on the *Models* property name to mirror how all other server error handling and validation works
                            //so that client end consumes it properly (fieldkey is just for the UI and showing / hiding overall form values)
                            object propertyValue = proposedObject.GetType().GetProperty(RequiredPropertyName).GetValue(proposedObject, null);
                            if (propertyValue == null)
                                biz.AddError(ApiErrorCode.VALIDATION_CUSTOM_REQUIRED_EMPTY, RequiredPropertyName);
                            else
                            {

                                if (RequiredPropertyName == "Tags")
                                {
                                    if (((System.Collections.Generic.List<string>)propertyValue).Count == 0)
                                    {
                                        biz.AddError(ApiErrorCode.VALIDATION_CUSTOM_REQUIRED_EMPTY, RequiredPropertyName);
                                    }
                                }
                                else
                                if (string.IsNullOrWhiteSpace(propertyValue.ToString()))
                                    biz.AddError(ApiErrorCode.VALIDATION_CUSTOM_REQUIRED_EMPTY, RequiredPropertyName);
                            }
                        }
                    }
                }
            }
        }
    }//eoc
}//ens
