using System.Reflection;
using System.Reflection.Emit;
using Hotline.Share.Dtos;
using Hotline.Share.Dtos.Order;
using MiniExcelLibs.Attributes;
using XF.Domain.Extensions;

namespace Hotline.Tools;

public class DynamicClassHelper
{
    public static Type CreateDynamicClass(List<ColumnInfo> propInfos)
    {
        string className = "DynamicClass";
        AssemblyName assemblyName = new AssemblyName("DynamicAssembly");
        AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
        TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public);

        foreach (var property in propInfos.OrderBy(d => d.Sort).ToList())
        {
            var propertyName = property.Prop.Trim().ToPascalCase();
            var propertyType = typeof(string);

            FieldBuilder fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
            PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);

            MethodAttributes getSetAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

            MethodBuilder getMethodBuilder = typeBuilder.DefineMethod("get_" + propertyName, getSetAttributes, propertyType, Type.EmptyTypes);
            ILGenerator getIL = getMethodBuilder.GetILGenerator();
            getIL.Emit(OpCodes.Ldarg_0);
            getIL.Emit(OpCodes.Ldfld, fieldBuilder);
            getIL.Emit(OpCodes.Ret);

            MethodBuilder setMethodBuilder = typeBuilder.DefineMethod("set_" + propertyName, getSetAttributes, null, new Type[] { propertyType });
            ILGenerator setIL = setMethodBuilder.GetILGenerator();
            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldarg_1);
            setIL.Emit(OpCodes.Stfld, fieldBuilder);
            setIL.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getMethodBuilder);
            propertyBuilder.SetSetMethod(setMethodBuilder);

            var columnNameCtorInfo = typeof(ExcelColumnNameAttribute).GetConstructor(new Type[] { typeof(string), typeof(string[]) });
            var attributeBuilder = new CustomAttributeBuilder(columnNameCtorInfo, new object[] { property.Name, null });
            propertyBuilder.SetCustomAttribute(attributeBuilder);
        }

        Type generatedType = typeBuilder.CreateType();
        return generatedType;
    }

    public static Type CreateDynamicClass<TSource>(List<ColumnInfo> propInfos)
    {
        string className = "DynamicClass";
        AssemblyName assemblyName = new AssemblyName("DynamicAssembly");
        AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
        TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public);
        var sourceProps = typeof(TSource).GetProperties();

        InitTypeBuilder(propInfos, sourceProps, typeBuilder);

        Type generatedType = typeBuilder.CreateType();
        return generatedType;
    }

    public static Type CreateDynamicClass(List<ColumnInfo> propInfos, Type tSource)
    {
        string className = "DynamicClass";
        AssemblyName assemblyName = new AssemblyName("DynamicAssembly");
        AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
        TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public);
        var sourceProps = tSource.GetProperties();

        InitTypeBuilder(propInfos, sourceProps, typeBuilder);

        Type generatedType = typeBuilder.CreateType();
        return generatedType;
    }

    private static void InitTypeBuilder(List<ColumnInfo> propInfos, PropertyInfo[] sourceProps, TypeBuilder typeBuilder)
    {
        foreach (var property in propInfos.OrderBy(d => d.Sort).ToList())
        {
            var propertyName = property.Prop.Trim().ToPascalCase();
            //var propertyType = sourceProps.FirstOrDefault(d => d.Name == propertyName)?.PropertyType ?? typeof(string);
            var propertyType = GetPropertyType(property.Prop, sourceProps);

            FieldBuilder fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
            PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);

            MethodAttributes getSetAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

            MethodBuilder getMethodBuilder = typeBuilder.DefineMethod("get_" + propertyName, getSetAttributes, propertyType, Type.EmptyTypes);
            ILGenerator getIL = getMethodBuilder.GetILGenerator();
            getIL.Emit(OpCodes.Ldarg_0);
            getIL.Emit(OpCodes.Ldfld, fieldBuilder);
            getIL.Emit(OpCodes.Ret);

            MethodBuilder setMethodBuilder = typeBuilder.DefineMethod("set_" + propertyName, getSetAttributes, null, new Type[] { propertyType });
            ILGenerator setIL = setMethodBuilder.GetILGenerator();
            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldarg_1);
            setIL.Emit(OpCodes.Stfld, fieldBuilder);
            setIL.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getMethodBuilder);
            propertyBuilder.SetSetMethod(setMethodBuilder);

            var columnNameCtorInfo = typeof(ExcelColumnNameAttribute).GetConstructor(new Type[] { typeof(string), typeof(string[]) });
            var attributeBuilder = new CustomAttributeBuilder(columnNameCtorInfo, new object[] { property.Name, null });
            propertyBuilder.SetCustomAttribute(attributeBuilder);

            if (propertyType == typeof(DateTime) || propertyType == typeof(DateTime?))
            {
                var columnExcelFormatCtorInfo = typeof(ExcelFormatAttribute).GetConstructor(new Type[] { typeof(string) });
                var excelFormatAttributeBuilder = new CustomAttributeBuilder(columnExcelFormatCtorInfo, new object[] { "yyyy-MM-dd HH:mm:ss" });
                propertyBuilder.SetCustomAttribute(excelFormatAttributeBuilder);
            }
        }
    }

    private static Type GetPropertyType(string propName, PropertyInfo[] sourceProps)
    {
        var propNameArray = propName.Split('.', StringSplitOptions.TrimEntries)
            .Select(d => d.ToPascalCase())
            .ToList();
        if (propNameArray.Count == 1)
        {
            return sourceProps.FirstOrDefault(d => d.Name == propNameArray[0])?.PropertyType ?? typeof(string);
        }
        else
        {
            var prop = sourceProps.FirstOrDefault(d => d.Name == propNameArray[0]);
            if (prop == null)
                return typeof(string);
            return GetPropertyType(string.Join(".", propNameArray.Skip(1)), prop.PropertyType.GetProperties());
        }
    }
}
