using DocumentFormat.OpenXml.Office2010.Ink;
using Hotline.Application.ExportExcel;
using Hotline.Share.Attributes;
using Hotline.Share.Dtos.Order;
using Hotline.Share.Tools;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using System.ComponentModel;
using System.Reflection;
using XF.Domain.Exceptions;

namespace Hotline.Api.Controllers.ExportData;

/// <summary>
/// 只要URL结尾是 export_excel 的请求都会进入这个控制器
/// 逻辑:
///     1. 通过URL找到对应的Controller和Action
///     2. 通过Controller和Action找到对应的ApplicationService
///        比如: RedPackController.GetRedPackList 就会去查找 RedPackApplication.GetRedPackList 方法
///     3. 通过反射调用对应的方法
///     4. ApplicationService 的方法返回的必须是 ISugarQueryable
///     5. ApplicationService 中方法的ExportExcel属性会作为导出的文件名
/// </summary>
[ApiController]
[Route("{*path:regex(.*export_excel$)}")]
public class ExportDataController : BaseController
{

    private readonly IServiceProvider _serviceProvider;
    private readonly EndpointDataSource _endpointDataSource;
    private readonly IExportApplication _exportApplication;

    public ExportDataController(IServiceProvider serviceProvider, EndpointDataSource endpointDataSource, IExportApplication exportApplication)
    {
        _serviceProvider = serviceProvider;
        _endpointDataSource = endpointDataSource;
        _exportApplication = exportApplication;
    }

    /// <summary>
    /// 动态导出数据
    /// </summary>
    /// <returns></returns>
    [HttpPost, HttpGet]
    public async Task<FileStreamResult> HandleExportPost()
    {
        var fullPath = HttpContext.Request.Path.Value;
        var originalPath = fullPath?.Substring(0, fullPath.LastIndexOf("/export_excel")).TrimStart('/');
        if (string.IsNullOrEmpty(originalPath))
            throw UserFriendlyException.SameMessage("无效的URL地址" + fullPath);

        var matchingEndpoints = _endpointDataSource.Endpoints
            .OfType<RouteEndpoint>()
            .Where(endpoint => endpoint.RoutePattern.RawText == originalPath)
            .ToList()
            ?? throw UserFriendlyException.SameMessage($"根据URL查询路由失败: {originalPath}");
        var matchingEndpoint = matchingEndpoints.FirstOrDefault();
        if (matchingEndpoints.Count > 1)
        {
            matchingEndpoint = matchingEndpoints.FirstOrDefault(m => m.DisplayName.Contains("Get"));
        }
        if (matchingEndpoint == null)
            throw UserFriendlyException.SameMessage($"根据URL查询路由失败: {originalPath}");

        var controllerName = matchingEndpoint.RoutePattern.RequiredValues["controller"]?.ToString();
        var actionName = matchingEndpoint.RoutePattern.RequiredValues["action"]?.ToString();

        var applicationServiceType = GetApplicationServiceType(controllerName)
            ?? throw UserFriendlyException.SameMessage($"根据名称查找方法失败. '{controllerName}'");

        var method = applicationServiceType.GetMethod(actionName);
        if (method == null)
        {
            method = applicationServiceType.GetMethod(actionName + "Async");
            if (method == null)
                throw UserFriendlyException.SameMessage($"根据方法名查找方法失败. '{actionName}'");
        }

        var serviceInstance = _serviceProvider.GetService(applicationServiceType);
        if (serviceInstance == null)
        {
            serviceInstance = _serviceProvider.GetService(applicationServiceType.GetInterfaces()[0]);
            if (serviceInstance == null)
                throw UserFriendlyException.SameMessage($"获取注入失败. '{applicationServiceType.Name}'.");
        }

        var parameters = method.GetParameters();

        using var reader = new StreamReader(HttpContext.Request.Body);
        var body = await reader.ReadToEndAsync();
        var param = parameters[0];
        var genericType = typeof(ExportExcelDto<>).MakeGenericType(param.ParameterType);
        var exportData = body.FromJson(genericType);
        var queryDto = genericType.GetProperty("QueryDto")?.GetValue(exportData);
        var isExportAll = genericType.GetProperty("IsExportAll")?.GetValue(exportData);
        int? pageIndex = param.ParameterType.GetProperty("PageIndex")?.GetValue(queryDto) as int?;
        var pageSize = param.ParameterType.GetProperty("PageSize")?.GetValue(queryDto);
        var result = method.Invoke(serviceInstance, [queryDto]);

        var returnType = method.ReturnType.GetGenericArguments()[0];
        var fileName = method.GetFileName() + "_";
        var totalName = method.GetTotalName();
        if (pageIndex == null || pageSize == null)
        {
            pageSize = param.ParameterType.GetProperty("QueryCount")?.GetValue(queryDto);
            pageIndex = param.ParameterType.GetProperty("QueryIndex")?.GetValue(queryDto) as int?;
            if (pageIndex == null || pageSize == null)
            {
                isExportAll = true;
                pageIndex = 1;
                pageSize = 20;
            }
            else
            {
                pageIndex += 1;
            }
        }

        return _exportApplication.GetExcelFile(returnType, genericType, exportData, ConvertToList(result, (bool)isExportAll, (int)pageIndex, (int)pageSize), fileName, totalName);
    }

    public static List<object>? ConvertToList(object? result, bool isExportAll, int pageIndex, int pageSize)
    {
        if (result == null)
        {
            return null;
        }

        var type = result.GetType();
        if (!type.IsGenericType || type.GetGenericTypeDefinition() == typeof(List<>))
        {
            return (result as IEnumerable<object>).ToList();
        }

        if (!type.IsGenericType || type.GetGenericTypeDefinition() != typeof(PostgreSQLQueryable<>))
        {
            throw UserFriendlyException.SameMessage("被导出方法的返回类型不是 ISugarQueryable");
        }

        if (isExportAll)
        {
            var toListMethod = type.GetMethods()
            .Where(m => m.Name == "ToList" && m.GetParameters().Length == 0)
            .First();
            var list = toListMethod.Invoke(result, null) as IEnumerable<object>;
            return list?.ToList();
        }
        else
        {
            var toListMethod = type.GetMethods()
            .Where(m => m.Name == "ToPageList" && m.GetParameters().Length == 2)
            .First();
            var list = toListMethod.Invoke(result, [pageIndex, pageSize]) as IEnumerable<object>;
            return list?.ToList();
        }
    }

    private Type GetApplicationServiceType(string controllerName)
    {
        var type = AppDomain.CurrentDomain.GetAssemblies().ToList()
        .SelectMany(d => d.GetTypes())
           .Where(d => d.Name.StartsWith(controllerName) && d.Name.EndsWith("Application"))
           .FirstOrDefault();
        if (type == null)
            type = AppDomain.CurrentDomain.GetAssemblies().ToList()
                .SelectMany(d => d.GetTypes())
                .Where(d => d.Name.StartsWith("Default" + controllerName) && d.Name.EndsWith("Application"))
                .FirstOrDefault();
        return type;
    }
}
