using Hotline.Application.FlowEngine;
using Hotline.Caching.Interfaces;
using Hotline.CallCenter.Calls;
using Hotline.CallCenter.Configs;
using Hotline.CallCenter.Devices;
using Hotline.CallCenter.Ivrs;
using Hotline.CallCenter.Tels;
using Hotline.Configurations;
using Hotline.FlowEngine.WorkflowModules;
using Hotline.FlowEngine.Workflows;
using Hotline.Settings;
using Hotline.Share.Dtos;
using Hotline.Share.Dtos.CallCenter;
using Hotline.Share.Dtos.FlowEngine;
using Hotline.Share.Dtos.FlowEngine.Workflow;
using Hotline.Share.Dtos.Trunk;
using Hotline.Share.Enums.CallCenter;
using Hotline.Share.Requests;
using Hotline.Users;
using MapsterMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using SqlSugar;
using Wex.Sdk;
using Wex.Sdk.Tel;
using XF.Domain.Authentications;
using XF.Domain.Cache;
using XF.Domain.Exceptions;
using XF.Domain.Repository;
using XF.Utility.EnumExtensions;

namespace Hotline.Api.Controllers
{
    /// <summary>
    /// 话机设备管理
    /// </summary>
    public class PbxController : BaseController
    {
        private readonly ITelRepository _telRepository;
        private readonly ITelRestRepository _telRestRepository;
        private readonly ITelDomainService _telDomainService;
        private readonly ITypedCache<Tel> _cacheTel;
        private readonly ITypedCache<TelGroup> _cacheTelGroup;
        private readonly ITelGroupRepository _telGroupRepository;
        private readonly IMapper _mapper;
        private readonly IWorkRepository _workRepository;
        private readonly INewRockDeviceManager _newRockDeviceManager;
        private readonly IUserCacheManager _userCacheManager;
        private readonly ISessionContext _sessionContext;
        private readonly ICallRepository _callRepository;
        private readonly IRepository<TrunkIvrManager> _trunkIvrManagerRepository;
        private readonly IIvrCategoryRepository _ivrCategoryRepository;
        private readonly IWorkflowApplication _workflowApplication;
        private readonly IWorkflowDomainService _workflowDomainService;
        private readonly ISystemSettingCacheManager _systemSettingCacheManager;
        private readonly IIvrDomainService _ivrDomainService;
        private readonly IIvrCacheManager _ivrCacheManager;
        private readonly ILogger<TelController> _logger;
        private readonly ICallDetailRepository _callDetailRepository;
        private readonly IRepository<User> _userRepository;
        private readonly IWexClient _wexClient;
        private readonly IWexTelGroupRepository _wexTelGroupRepository;
        private readonly IOptionsSnapshot<CallCenterConfiguration> _options;


        public PbxController(
            IServiceProvider serviceProvider,
            ITelRepository telRepository,
            ITelRestRepository telRestRepository,
            ITelDomainService telDomainService,
            ITypedCache<Tel> cacheTel,
            ITypedCache<TelGroup> cacheTelGroup,
            ITelGroupRepository telGroupRepository,
            IMapper mapper,
            IWorkRepository workRepository,
            IUserCacheManager userCacheManager,
            ISessionContext sessionContext,
            ICallRepository callRepository,
            IRepository<TrunkIvrManager> trunkIvrManagerRepository,
            IIvrCategoryRepository ivrCategoryRepository,
            IWorkflowApplication workflowApplication,
            IWorkflowDomainService workflowDomainService,
            ISystemSettingCacheManager systemSettingCacheManager,
            IIvrDomainService ivrDomainService,
            IIvrCacheManager ivrCacheManager,
            ILogger<TelController> logger,
            ICallDetailRepository callDetailRepository,
            IRepository<User> userRepository,
            IWexClient wexClient,
            IWexTelGroupRepository wexTelGroupRepository,
            IOptionsSnapshot<AppConfiguration> appOptions,
            IOptionsSnapshot<CallCenterConfiguration> options)
        {
            _telRepository = telRepository;
            _telRestRepository = telRestRepository;
            _telDomainService = telDomainService;
            _cacheTel = cacheTel;
            _cacheTelGroup = cacheTelGroup;
            _telGroupRepository = telGroupRepository;
            _mapper = mapper;
            _workRepository = workRepository;
            _userCacheManager = userCacheManager;
            _sessionContext = sessionContext;
            _callRepository = callRepository;
            _trunkIvrManagerRepository = trunkIvrManagerRepository;
            _ivrCategoryRepository = ivrCategoryRepository;
            _workflowApplication = workflowApplication;
            _workflowDomainService = workflowDomainService;
            _systemSettingCacheManager = systemSettingCacheManager;
            _ivrDomainService = ivrDomainService;
            _ivrCacheManager = ivrCacheManager;
            _logger = logger;
            _callDetailRepository = callDetailRepository;
            _userRepository = userRepository;
            _wexClient = wexClient;
            _wexTelGroupRepository = wexTelGroupRepository;
            _options = options;
            if (appOptions.Value.GetDefaultAppScopeConfiguration().CallCenterType == AppDefaults.CallCenterType.XunShi)
            {
                _newRockDeviceManager = serviceProvider.GetRequiredService<INewRockDeviceManager>();
            }
        }

        #region 话机

        /// <summary>
        /// 根据设备自动同步分机数据到数据库
        /// </summary>
        /// <returns></returns>
        [HttpPut("sync-tels")]
        public async Task SyncTelsAsync()
        {
            var dbTels = await _telRepository.QueryAsync();
            foreach (var dbTel in dbTels)
            {
                _cacheTel.Remove(dbTel.No);
            }
            //await _telRepository.RemoveRangeAsync(dbTels);
            var tels = await _telDomainService.QueryTelsAsync(HttpContext.RequestAborted);
            var list = tels.ExceptBy(dbTels.Select(e => e.No), x => x.No).ToList();
            var delList = dbTels.ExceptBy(tels.Select(e => e.No), x => x.No).ToList();

            for (int i = 0; i < delList.Count; i++)
            {
                delList[i].SoftDelete();
            }
            await _telRepository.UpdateRangeAsync(delList);
            await _telRepository.AddRangeAsync(list);
        }

        /// <summary>
        /// 查询所有分机
        /// </summary>
        /// <returns></returns>
        [HttpGet("query-tels")]
        public async Task<IReadOnlyList<TelDto>> QueryTels()
        {
            var tels = await _telRepository.QueryExtAsync(d => true, d => d.Includes(x => x.Groups));
            return _mapper.Map<IReadOnlyList<TelDto>>(tels);
        }


        #endregion

        #region 分机组

        /// <summary>
        /// 页面基础信息
        /// </summary>
        /// <returns></returns>
        [HttpGet("base-info-group")]
        public async Task<dynamic> GetBaseInfoGroup()
        {
            return new
            {
                Distributions = EnumExts.GetDescriptions<EDistribution>()
            };
        }

        /// <summary>
        /// 查询所有分机组
        /// </summary>
        /// <returns></returns>
        [HttpGet("query-telgroups")]
        public async Task<IReadOnlyList<TelGroupDto>> QueryTelGroups()
        {
            var groups = await _telGroupRepository.QueryExtAsync(d => true, d => d.Includes(x => x.Tels));
            return _mapper.Map<IReadOnlyList<TelGroupDto>>(groups);
        }

        /// <summary>
        /// 新增分机组
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost("add-telgroup")]
        public async Task AddTelGroup([FromBody] AddTelGroupDto dto)
        {
            var works = await _workRepository.QueryAsync(d => dto.TelNos.Contains(d.TelNo) && !d.EndTime.HasValue);

            await _newRockDeviceManager.AssginConfigGroupAsync(
                _options.Value.NewRock, dto.No,
                dto.Distribution,
                ext: works.Select(d => d.TelNo).ToList(),
                voiceFile: dto.Voice ?? null,
            cancellationToken: HttpContext.RequestAborted);

            var group = _mapper.Map<TelGroup>(dto);
            var tels = await _telRepository.QueryAsync(d => dto.TelNos.Contains(d.No));
            group.Tels = tels;
            await _telGroupRepository.AddNavTelsAsync(group, HttpContext.RequestAborted);
            _cacheTelGroup.Remove(dto.No);
        }

        /// <summary>
        /// 更新分机组
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPut("update-telgroup")]
        public async Task UpdateTelGroup([FromBody] UpdateTelGroupDto dto)
        {
            var works = await _workRepository.QueryAsync(d => dto.TelNos.Contains(d.TelNo) && !d.EndTime.HasValue);

            await _newRockDeviceManager.AssginConfigGroupAsync(
                _options.Value.NewRock, dto.No,
                dto.Distribution,
                ext: works.Select(d => d.TelNo).ToList(),
                voiceFile: dto.Voice ?? null,
            cancellationToken: HttpContext.RequestAborted);

            var group = _mapper.Map<TelGroup>(dto);
            var tels = await _telRepository.QueryAsync(d => dto.TelNos.Contains(d.No));
            group.Tels = tels;
            await _telGroupRepository.UpdateNavTelsAsync(group, HttpContext.RequestAborted);
            _cacheTelGroup.Remove(dto.No);
        }

        #endregion

        #region 话机设备操作

        /// <summary>
        /// 查询小休流程开启参数
        /// </summary>
        /// <returns></returns>
        [HttpGet("flow-start")]
        public async Task<NextStepsDto> GetFlowStartOptionsAsync()
        {
            //return await _workflowApplication.GetStartOptionsAsync(WorkflowModuleConsts.TelRestApply,
            //    HttpContext.RequestAborted);

            return await _workflowApplication.GetStartStepsAsync(WorkflowModuleConsts.TelRestApply,
                HttpContext.RequestAborted);
        }

        /// <summary>
        /// 分机休息
        /// </summary>
        /// <returns></returns>
        [HttpPost("rest")]
        public async Task Rest([FromBody] StartRestDto dto)
        {
            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
            if (work is null)
                throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");

            bool isCanApply = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.IsRestApproval).SettingValue[0]);

            if (isCanApply)
            {
                var isApply = await _telRestRepository.IsApplyingAsync(work.TelNo, HttpContext.RequestAborted);
                if (isApply)
                    throw new UserFriendlyException("分机休息申请正在审核阶段");
            }

            var isResting = await _telRestRepository.IsRestingAsync(work.TelNo, HttpContext.RequestAborted);
            if (isResting)
                throw new UserFriendlyException("当前坐席正在休息");

            var user = _userRepository.Get(work.UserId);
            var telRest = new TelRest(work.TelId, work.TelNo, work.UserId, work.UserName, dto.Reason, isCanApply, user.StaffNo);
            await _telRestRepository.AddAsync(telRest, HttpContext.RequestAborted);
            if (!isCanApply)
            {
                await _telDomainService.TelRestApplyPassAsync(telRest.Id, HttpContext.RequestAborted);
            }
            else
            {
                var startWorkflowDto = _mapper.Map<StartWorkflowDto>(dto);
                startWorkflowDto.DefinitionModuleCode = WorkflowModuleConsts.TelRestApply;
                startWorkflowDto.Opinion = dto.Reason;
                startWorkflowDto.Title = "分机休息申请流程";
                // await _workflowApplication.StartWorkflowAsync(startWorkflowDto, telRest.Id, cancellationToken: HttpContext.RequestAborted);
                //await _workflowDomainService.StartAsync(startWorkflowDto, telRest.Id, cancellationToken: HttpContext.RequestAborted);
                await _workflowDomainService.StartToFirstStepAsync(startWorkflowDto, telRest.Id, cancellationToken: HttpContext.RequestAborted);
            }
        }

        #region 威尔信小休

        /// <summary>
        /// 分机休息流程开始
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost("rest-flow")]
        public async Task RestFlow([FromBody] StartRestDto dto)
        {
            var tel = await _wexClient.QueryTelsAsync(new QueryTelRequest() { StaffNo = _sessionContext.StaffNo }, HttpContext.RequestAborted);
            if (tel.Data == null || tel.Data.Count == 0)
                throw UserFriendlyException.SameMessage("未找到分机信息");

            if (tel.Data[0].Sigin == 0)
                throw UserFriendlyException.SameMessage("分机未签入，不能休息");

            var isApply = await _telRestRepository.IsApplyingAsync(tel.Data[0].TelNo, HttpContext.RequestAborted);
            if (isApply)
                throw UserFriendlyException.SameMessage("分机休息申请正在审核阶段");

            var isResting = await _telRestRepository.IsRestingAsync(tel.Data[0].TelNo, HttpContext.RequestAborted);
            if (isResting)
                throw new UserFriendlyException("当前坐席正在休息");

            var telRest = new TelRest(tel.Data[0].TelNo, tel.Data[0].TelNo, _sessionContext.RequiredUserId, _sessionContext.UserName, dto.Reason, true, _sessionContext.StaffNo);
            await _telRestRepository.AddAsync(telRest, HttpContext.RequestAborted);

            var startWorkflowDto = _mapper.Map<StartWorkflowDto>(dto);
            startWorkflowDto.DefinitionModuleCode = WorkflowModuleConsts.TelRestApply;
            startWorkflowDto.Opinion = dto.Reason;
            startWorkflowDto.Title = "分机休息申请流程";
            // await _workflowApplication.StartWorkflowAsync(startWorkflowDto, telRest.Id, cancellationToken: HttpContext.RequestAborted);
            //await _workflowDomainService.StartAsync(startWorkflowDto, telRest.Id, cancellationToken: HttpContext.RequestAborted);
            await _workflowDomainService.StartToFirstStepAsync(startWorkflowDto, telRest.Id, cancellationToken: HttpContext.RequestAborted);
        }


        /// <summary>
        /// 开始分机休息
        /// </summary>
        /// <returns></returns>
        [HttpGet("begin-rest")]
        public async Task BeginRest()
        {
            var tel = await _wexClient.QueryTelsAsync(new QueryTelRequest() { StaffNo = _sessionContext.StaffNo }, HttpContext.RequestAborted);

            if (tel.Data == null || tel.Data.Count == 0)
                throw UserFriendlyException.SameMessage("未找到分机信息");

            if (tel.Data[0].Sigin == 0)
                throw UserFriendlyException.SameMessage("分机未签入，不能休息");

            var telRest = await _telRestRepository.GetAsync(x => !x.EndTime.HasValue && !x.StartTime.HasValue && x.TelNo == tel.Data[0].TelNo, HttpContext.RequestAborted);

            if (telRest == null)
                throw new UserFriendlyException($"错误");

            //if (string.IsNullOrEmpty(id))
            //    throw UserFriendlyException.SameMessage("无效分机休息编号");
            //var telRest = await _telRestRepository.GetAsync(id, HttpContext.RequestAborted);
            //if (telRest == null)
            //    throw new UserFriendlyException($"无效分机休息编号, telRestId: {id}", "无效分机休息编号");

            telRest.ApplyStatus = ETelRestApplyStatus.Resting;
            telRest.StartTime = DateTime.Now;
            await _telRestRepository.UpdateAsync(telRest, HttpContext.RequestAborted);
        }


        /// <summary>
        /// 分机休息
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost("rest-add")]
        public async Task Rest([FromBody] ResstDto dto)
        {
            var tel = await _wexClient.QueryTelsAsync(new QueryTelRequest() { StaffNo = _sessionContext.StaffNo }, HttpContext.RequestAborted);

            if (tel.Data == null || tel.Data.Count == 0)
                throw UserFriendlyException.SameMessage("未找到分机信息");

            if (tel.Data[0].Sigin == 0)
                throw UserFriendlyException.SameMessage("分机未签入，不能休息");

            var telRest = new TelRest(tel.Data[0].TelNo, tel.Data[0].TelNo, _sessionContext.RequiredUserId, _sessionContext.UserName, dto.Reason, false, _sessionContext.StaffNo);
            await _telRestRepository.AddAsync(telRest, HttpContext.RequestAborted);
        }

        /// <summary>
        /// 删除分机休息
        /// </summary>
        /// <returns></returns>
        /// <exception cref="UserFriendlyException"></exception>
        [HttpGet("rest-del")]
        public async Task DelRest()
        {
            var tel = await _wexClient.QueryTelsAsync(new QueryTelRequest() { StaffNo = _sessionContext.StaffNo }, HttpContext.RequestAborted);

            if (tel.Data == null || tel.Data.Count == 0)
                throw UserFriendlyException.SameMessage("未找到分机信息");

            if (tel.Data[0].Sigin == 0)
                throw UserFriendlyException.SameMessage("分机未签入，不能休息");

            var restingTel = await _telRestRepository.GetAsync(d => d.TelId == tel.Data[0].TelNo && !d.EndTime.HasValue, HttpContext.RequestAborted);
            if (restingTel is null)
                throw new UserFriendlyException("未查询到分机休息信息");
            await _telRestRepository.RemoveAsync(restingTel.Id, false, HttpContext.RequestAborted);
        }

        /// <summary>
        /// 分机结束休息（威尔信）
        /// </summary>
        /// <returns></returns>
        [HttpGet("un-rest-wex")]
        public async Task<TelRestDto> UnRestWEX()
        {
            var tel = await _wexClient.QueryTelsAsync(new QueryTelRequest() { StaffNo = _sessionContext.StaffNo }, HttpContext.RequestAborted);

            if (tel.Data == null || tel.Data.Count == 0)
                throw UserFriendlyException.SameMessage("未找到分机信息");

            if (tel.Data[0].Sigin == 0)
                throw UserFriendlyException.SameMessage("分机未签入，不能休息");

            var telRest = await _telDomainService.UnRestWexAsync(tel.Data[0].TelNo, HttpContext.RequestAborted);
            return _mapper.Map<TelRestDto>(telRest);
        }

        #endregion




        /// <summary>
        /// 分机结束休息
        /// </summary>
        [HttpPut("un-rest")]
        public async Task<TelRestDto> UnRest()
        {
            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
            if (work is null)
                throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
            var telRest = await _telDomainService.UnRestAsync(work.TelId, HttpContext.RequestAborted);
            return _mapper.Map<TelRestDto>(telRest);
        }

        /// <summary>
        /// 保持通话
        /// </summary>
        [HttpPut("hold")]
        public async Task Hold(string callId)
        {
            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
            if (work is null)
                throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
            await _telDomainService.HoldAsync(work.TelId, _sessionContext.RequiredUserId, _sessionContext.UserName, callId, HttpContext.RequestAborted);
        }

        /// <summary>
        /// 恢复通话(解除hold状态)
        /// </summary>
        [HttpPut("un-hold")]
        public async Task UnHold(string callId)
        {
            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
            if (work is null)
                throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
            await _telDomainService.UnHoldAsync(work.TelId, _sessionContext.RequiredUserId, callId, HttpContext.RequestAborted);
        }
        /// <summary>
        /// 分机呼分机
        /// </summary>
        /// <returns></returns>
        [HttpPost("tel-to-tel")]
        public async Task TelToTel([FromBody] TelToTelDto dto)
        {
            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
            if (work is null)
                throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
            //获取对方是否在工作
            var toWork = _userCacheManager.GetWorkByTel(dto.TelNo);
            if (toWork is null)
                throw UserFriendlyException.SameMessage("转接分机未进行工作");

            //判断分机状态
            var telState = await _newRockDeviceManager.QueryTelState(_options.Value.NewRock, dto.TelNo, HttpContext.RequestAborted);
            if (telState != ETelStatus.Ready)
                throw UserFriendlyException.SameMessage("被叫分机不在线或正在通话中");

            bool isRest = await _telRestRepository.IsRestingAsync(dto.TelNo, HttpContext.RequestAborted);
            if (isRest)
                throw new UserFriendlyException("被叫分机正在休息不能转接");

            await _newRockDeviceManager.ExtToExtAsync(_options.Value.NewRock, work.TelNo, dto.TelNo, HttpContext.RequestAborted);
        }

        /// <summary>
        /// 分机拨打外部电话
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost("tel-to-outer")]
        public async Task TelToOuter([FromBody] TelToOuterDto dto)
        {
            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
            if (work is null)
                throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
            await _newRockDeviceManager.ExtToOuterAsync(_options.Value.NewRock, work.TelNo, dto.OuterNo, HttpContext.RequestAborted);
        }

        /// <summary>
        /// 指定模拟外线外呼（分机拨打外部电话）
        /// OM设备多个外线时调用此接口
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost("tel-to-outer-line")]
        public async Task TelToOuterByLine([FromBody] TelToOuterByLineDto dto)
        {
            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
            if (work is null)
                throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
            string newOuter = dto.LineId + "," + dto.OuterNo;
            await _newRockDeviceManager.ExtToOuterAsync(_options.Value.NewRock, work.TelNo, newOuter, HttpContext.RequestAborted);
        }

        /// <summary>
        /// 来电转分机
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost("visitor-to-tel")]
        public async Task VisitorToTel([FromBody] VisitorToTelDto dto)
        {
            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
            if (work is null)
                throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
            var toWork = _userCacheManager.GetWorkByTel(dto.TelNo);
            if (toWork is null)
                throw UserFriendlyException.SameMessage("转接分机未进行工作");

            var totelState = await _newRockDeviceManager.QueryTelState(_options.Value.NewRock, dto.TelNo, HttpContext.RequestAborted);
            if (totelState != ETelStatus.Ready)
                throw UserFriendlyException.SameMessage("被叫分机不在线或正在通话中");

            bool isRest = await _telRestRepository.IsRestingAsync(dto.TelNo, HttpContext.RequestAborted);
            if (isRest)
                throw new UserFriendlyException("被叫分机正在休息不能转接");


            var tel = await _newRockDeviceManager.QueryTelAsync(_options.Value.NewRock, work.TelNo, HttpContext.RequestAborted);
            if (!string.IsNullOrEmpty(tel.ConversationId))
                await _newRockDeviceManager.VisitorToExtAsync(_options.Value.NewRock, tel.ConversationId, dto.TelNo, HttpContext.RequestAborted);
            else
                throw UserFriendlyException.SameMessage("当前分机没有通话");
        }

        /// <summary>
        /// 来电转外部电话
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost("visitor-to-outer")]
        public async Task VisitorToOuter([FromBody] VisitorToOuterDto dto)
        {
            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
            if (work is null)
                throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
            var tel = await _newRockDeviceManager.QueryTelAsync(_options.Value.NewRock, work.TelNo, HttpContext.RequestAborted);
            if (!string.IsNullOrEmpty(tel.ConversationId))
                await _newRockDeviceManager.VisitorToOuterAsync(_options.Value.NewRock, tel.ConversationId, dto.OuterNo, HttpContext.RequestAborted);
            else
                throw UserFriendlyException.SameMessage("当前分机没有通话");
        }


        /// <summary>
        /// 来电转分机组队列
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost("visitor-to-group")]
        public async Task VisitorToGroup([FromBody] VisitorToGroupDto dto)
        {
            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
            if (work is null)
                throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
            var tel = await _newRockDeviceManager.QueryTelAsync(_options.Value.NewRock, work.TelNo, HttpContext.RequestAborted);
            if (!string.IsNullOrEmpty(tel.ConversationId))
                await _newRockDeviceManager.VisitorToGroupAsync(_options.Value.NewRock, tel.ConversationId, dto.groupid, HttpContext.RequestAborted);
            else
                throw UserFriendlyException.SameMessage("当前分机没有通话");
        }

        /// <summary>
        /// 去电转外部电话
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        /// <exception cref="UserFriendlyException"></exception>
        [HttpPost("outer-to-outer")]
        public async Task OuterToOuter([FromBody] OuterToOuterDto dto)
        {
            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
            if (work is null)
                throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
            var tel = await _newRockDeviceManager.QueryTelAsync(_options.Value.NewRock, work.TelNo, HttpContext.RequestAborted);
            if (!string.IsNullOrEmpty(tel.ConversationId))
                await _newRockDeviceManager.OuterToOuterAsync(_options.Value.NewRock, tel.ConversationId, dto.OuterNo, HttpContext.RequestAborted);
            else
                throw UserFriendlyException.SameMessage("当前分机没有通话");
        }

        /// <summary>
        /// 去电转分机
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        /// <exception cref="UserFriendlyException"></exception>
        [HttpPost("outer-to-tel")]
        public async Task OuterToTel([FromBody] OuterToTelDto dto)
        {
            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
            if (work is null)
                throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
            var toWork = _userCacheManager.GetWorkByTel(dto.TelNo);
            if (toWork is null)
                throw UserFriendlyException.SameMessage("转接分机未进行工作");

            var totelState = await _newRockDeviceManager.QueryTelState(_options.Value.NewRock, dto.TelNo, HttpContext.RequestAborted);
            if (totelState != ETelStatus.Ready)
                throw UserFriendlyException.SameMessage("被叫分机不在线或正在通话中");

            bool isRest = await _telRestRepository.IsRestingAsync(dto.TelNo, HttpContext.RequestAborted);
            if (isRest)
                throw new UserFriendlyException("被叫分机正在休息不能转接");

            var tel = await _newRockDeviceManager.QueryTelAsync(_options.Value.NewRock, work.TelNo, HttpContext.RequestAborted);
            if (!string.IsNullOrEmpty(tel.ConversationId))
                await _newRockDeviceManager.OuterToExtAsync(_options.Value.NewRock, tel.ConversationId, dto.TelNo, HttpContext.RequestAborted);
            else
                throw UserFriendlyException.SameMessage("当前分机没有通话");
        }

        /// <summary>
        /// 三方会议
        /// 先建立两方通话，然后调用保持通话接口，拨通第三方分机，然后再调用三方会议接口
        /// 1. 分机 A 正在和 B 通话；
        /// 2. 分机 A 把原通话呼叫保持；
        /// 3. 分机 A 向 C 发起新的呼叫，并建立通话；
        /// 4. 此时，使用该 API 能够实现以分机 A 为主持方建立 A、B、C 的三方会议。
        /// </summary>
        /// <param name="dto">TelNo：会议发起方分机号</param>
        /// <returns></returns>
        /// <exception cref="UserFriendlyException"></exception>
        [HttpPost("meeting")]
        public async Task Conference([FromBody] ConferenceDto dto)
        {
            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
            if (work is null)
                throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
            var tel = await _newRockDeviceManager.QueryTelAsync(_options.Value.NewRock, work.TelNo, HttpContext.RequestAborted);
            if (!string.IsNullOrEmpty(tel.ConversationId))
                await _newRockDeviceManager.ConferenceMeetingAsync(_options.Value.NewRock, dto.TelNo, HttpContext.RequestAborted);
            else
                throw UserFriendlyException.SameMessage("当前分机没有通话");
        }


        #region 监听和强插

        /// <summary>
        /// 监听分机
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        [HttpPost("monitor-ext")]
        public async Task MonitorExt([FromBody] MonitorExtRequest request)
        {
            await _newRockDeviceManager.MonitorExtAsync(_options.Value.NewRock, request.firstTelNo, request.secondTelNo, HttpContext.RequestAborted);
        }


        /// <summary>
        /// 从监听到插播状态变换
        /// 1. 已知：分机 A 在监听分机 B 与其通话方的通话；
        /// 2. 执行分机 A 的从监听到插播状态变换的 API；
        /// 3. 执行成功时，分机 A 与分机 B 建立通话，分机 B 的原通话方听保持音。
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        [HttpPost("monitor-ext-to-talk")]
        public async Task MonitorExtToTalk([FromBody] MonitorExtToTalkRequest request)
        {
            await _newRockDeviceManager.MonitorExtToTalkAsync(_options.Value.NewRock, request.telNo, HttpContext.RequestAborted);
        }


        /// <summary>
        /// 从插播到监听状态变换
        /// 1. 已知：分机 A 在插播分机 B 的通话；
        /// 2. 执行分机 A 的从插播到监听状态变换的 API；
        /// 3. 执行成功时，分机 A 监听分机 B 及其原通话方的通话。
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        [HttpPost("monitor-ext-to-listen")]
        public async Task MonitorExtToListen([FromBody] MonitorExtToListenRequest request)
        {
            await _newRockDeviceManager.MonitorExtToListenAsync(_options.Value.NewRock, request.telNo, HttpContext.RequestAborted);
        }


        #endregion

        #region 强插

        /// <summary>
        /// 强插
        /// 1. 已知：分机 A 当前空闲，分机 B 正在通话中；
        /// 2. 执行分机 A 强插分机 B 的 API；
        /// 3. 执行成功时，分机 A 振铃，摘机后即可形成三方通话。
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        [HttpPost("bargein-ext")]
        public async Task BargeinExt([FromBody] BargeinExtRequest request)
        {
            await _newRockDeviceManager.BargeinExtAsync(_options.Value.NewRock, request.firstTelNo, request.secondTelNo, HttpContext.RequestAborted);
        }

        #endregion

        #region 强拆

        /// <summary>
        /// 强拆分机
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        [HttpPost("clear-ext")]
        public async Task ClearExt([FromBody] ClearExtRequest request)
        {
            //查询当前通话记录
            //var call = await _callRepository.GetAsync(request.CallId, HttpContext.RequestAborted);
            //if (call is null)
            //    throw UserFriendlyException.SameMessage("无效通话，无法挂断");
            //if (call.CallStatus == ECallStatus.Bye)
            //    throw UserFriendlyException.SameMessage("通话已结束");

            await _newRockDeviceManager.ClearExtAsync(_options.Value.NewRock, request.CallId, HttpContext.RequestAborted);
        }

        /// <summary>
        /// 强拆来电
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        [HttpPost("clear-visitor")]
        public async Task ClearVisitor([FromBody] ClearVisitorRequest request)
        {
            //查询当前通话记录
            var call = await _callRepository.GetAsync(request.CallId, HttpContext.RequestAborted);
            if (call is null)
                throw UserFriendlyException.SameMessage("无效通话，无法挂断");
            if (call.CallStatus == ECallStatus.Bye)
                throw UserFriendlyException.SameMessage("通话已结束");
            await _newRockDeviceManager.ClearVisitorAsync(_options.Value.NewRock, call.ConversationId, HttpContext.RequestAborted);
        }

        /// <summary>
        /// 强拆去电
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        [HttpPost("clear-outer")]
        public async Task ClearOuter([FromBody] ClearOuterRequest request)
        {
            //查询当前通话记录
            var call = await _callRepository.GetAsync(request.CallId, HttpContext.RequestAborted);
            if (call is null)
                throw UserFriendlyException.SameMessage("无效通话，无法挂断");
            if (call.CallStatus == ECallStatus.Bye)
                throw UserFriendlyException.SameMessage("通话已结束");
            await _newRockDeviceManager.ClearOuterAsync(_options.Value.NewRock, call.ConversationId, HttpContext.RequestAborted);
        }

        #endregion

        #region 静音和取消静音

        /// <summary>
        /// 静音
        /// </summary>
        /// <returns></returns>
        [HttpGet("mute")]
        public async Task Mute()
        {
            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
            if (work is null)
                throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
            await _newRockDeviceManager.MuteAsync(_options.Value.NewRock, work.TelNo, HttpContext.RequestAborted);
        }

        /// <summary>
        /// 取消静音
        /// </summary>
        /// <returns></returns>
        [HttpGet("unmute")]
        public async Task UnMute()
        {
            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
            if (work is null)
                throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
            await _newRockDeviceManager.UnMuteAsync(_options.Value.NewRock, work.TelNo, HttpContext.RequestAborted);
        }

        #endregion

        #endregion

        #region 线路管理

        /// <summary>
        /// 获取线路列表
        /// </summary>
        /// <returns></returns>
        [HttpGet("trunk-list")]
        public async Task<IReadOnlyList<TrunkIvrManager>> GetTrunkList()
        {
            return await _trunkIvrManagerRepository.Queryable()
                    .Includes(d => d.WorkCategoryModel)
                    .Includes(d => d.RestCategoryModel)
                    .Includes(d => d.WorkToGroupModel.MappingField(z => z.No, () => d.WorkToGroup))
                    .Includes(d => d.RestToGroupModel.MappingField(z => z.No, () => d.RestToGroup))
                    .ToListAsync();
        }

        /// <summary>
        /// 获取线路对象
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet("trunk/{id}")]
        public async Task<TrunkIvrManager> GetTrunk(string id)
        {
            var trunk = await _trunkIvrManagerRepository.GetAsync(id, HttpContext.RequestAborted);
            if (trunk is null)
                throw UserFriendlyException.SameMessage("无效线路");
            return trunk;
        }

        /// <summary>
        /// 新增线路
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost("addtrunk")]
        public async Task AddTrunk([FromBody] AddTrunkDto dto)
        {
            var trunk = _mapper.Map<TrunkIvrManager>(dto);
            await _trunkIvrManagerRepository.AddAsync(trunk, HttpContext.RequestAborted);
        }

        /// <summary>
        /// 修改线路
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost("updatetrunk")]
        public async Task UpdateTrunk([FromBody] UpdateTrunkDto dto)
        {
            var trunk = await _trunkIvrManagerRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
            if (trunk is null)
                throw UserFriendlyException.SameMessage("无效线路");

            _mapper.Map(dto, trunk);
            await _trunkIvrManagerRepository.UpdateAsync(trunk, HttpContext.RequestAborted);
        }

        /// <summary>
        /// 删除线路
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet("removetrunk/{id}")]
        public async Task RemoveTrunk(string id)
        {
            var trunk = await _trunkIvrManagerRepository.GetAsync(id, HttpContext.RequestAborted);
            if (trunk is null)
                throw UserFriendlyException.SameMessage("无效线路");
            await _trunkIvrManagerRepository.RemoveAsync(id, false, HttpContext.RequestAborted);
        }

        /// <summary>
        /// 线路页面基础信息
        /// </summary>
        /// <returns></returns>
        [HttpGet("trunk-page-info")]
        public async Task<object> TrunkPageInfo()
        {
            var ivr = await _ivrCategoryRepository.QueryAsync();
            var group = await _telGroupRepository.QueryAsync();
            var workDay = new List<WorkDayModel>();
            workDay.Add(new WorkDayModel() { weekName = "星期日", weekValue = "0" });
            workDay.Add(new WorkDayModel() { weekName = "星期一", weekValue = "1" });
            workDay.Add(new WorkDayModel() { weekName = "星期二", weekValue = "2" });
            workDay.Add(new WorkDayModel() { weekName = "星期三", weekValue = "3" });
            workDay.Add(new WorkDayModel() { weekName = "星期四", weekValue = "4" });
            workDay.Add(new WorkDayModel() { weekName = "星期五", weekValue = "5" });
            workDay.Add(new WorkDayModel() { weekName = "星期六", weekValue = "6" });
            return new { WorkCategorys = ivr, RestCategory = ivr, WorkToGroup = group, RestToGroup = group, WorkDay = workDay };
        }


        #endregion

        #region 话机业务

        /// <summary>
        /// 语音评价
        /// </summary>
        /// <param name="callId"></param>
        /// <returns></returns>
        [HttpGet("evaluate/{callId}")]
        public async Task Evaluate(string callId)
        {
            //检查通话是否存在
            var call = await _callRepository.GetAsync(callId, HttpContext.RequestAborted);
            if (call is null)
            {
                throw new UserFriendlyException("未找到当前通话");
            }
            if (call.CallDirection != ECallDirection.In)
            {
                throw new UserFriendlyException("当前通话不是来电，不能发送评价邀请");
            }
            if (call.CallStatus == ECallStatus.Bye)
            {
                throw new UserFriendlyException("当前通话已结束");
            }
            if (call.CallStatus != ECallStatus.Answered && call.CallStatus != ECallStatus.Answer)
            {
                throw new UserFriendlyException("当前未进行通话，不能发送评价邀请");
            }
            //获取配置
            var correct = _ivrDomainService.GetCorrectIvr(call.ToNo, true);
            if (correct is null)
                throw new UserFriendlyException("系统未配置评价，请联系管理员");
            //检查是否有评价录音配置
            var ivrList = await _ivrCacheManager.GetIvrsAsync(HttpContext.RequestAborted);
            if (!ivrList.Any())
                throw new UserFriendlyException("未查到任何ivr配置");
            var ivr = ivrList.First(x => x.IvrCategoryId == correct.ReturnValue && x.IsRoot);

            _logger.LogInformation("transfer to ivr.no:{ivrNo}", ivr.No);

            //写入子表
            var detail = new CallDetail()
            {
                CallId = call.Id,
                CallStatus = ECallStatus.Evaluate,
                ConversationId = call.ConversationId,
                EventName = "EVALUATE",
                FromNo = call.FromNo,
                ToNo = call.ToNo,
            };

            await _callDetailRepository.AddAsync(detail, HttpContext.RequestAborted);

            await _newRockDeviceManager.VisitorToMenuAsync(_options.Value.NewRock, call.ConversationId, ivr.No, HttpContext.RequestAborted);
        }

        #endregion

        #region 威尔信分机和分机组操作

        /// <summary>
        /// 查询分机
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpGet("query-tel-list")]
        public async Task<PagedDto<TelListPageDto>> QueryTelList([FromQuery] QueryTelListDto dto)
        {
            var rsp = await _wexClient.QueryTelsPageAsync(new QueryTelPageRequest() { PageIndex = dto.PageIndex, PageSize = dto.PageSize, input = new Input() { TelNo = dto.TelNo } }, HttpContext.RequestAborted);
            var telList = rsp?.Data;
            var count = rsp.Count;
            if (telList != null)
            {
                var telGroup = _wexTelGroupRepository.Queryable().ToList();

                var list = (from a in telList
                            join b in telGroup on a.TelNo equals b.TelNo into output
                            from j in output.DefaultIfEmpty()
                            select new TelListPageDto
                            {
                                Id = (j == null ? "" : j.Id),
                                TelNo = a.TelNo,
                                GroupName = (j == null ? "" : j.GroupName),
                                GroupId = (j == null ? 0 : j.GroupId)
                            }).ToList();
                return new PagedDto<TelListPageDto>(count, list);
            }
            return new PagedDto<TelListPageDto>(0, null);
        }

        /// <summary>
        /// 新增或修改分机关联分机组
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost("add-update-telgroup")]
        public async Task AddOrUpdateTelGroup([FromBody] AddOrUpdateTelGroupDto dto)
        {
            if (string.IsNullOrEmpty(dto.Id))
            {
                //新增
                var telGroup = new WexTelGroup();
                telGroup.TelNo = dto.TelNo;
                telGroup.GroupId = dto.GroupId;
                telGroup.GroupName = dto.GroupName;
                telGroup.ZuoGroupName = dto.ZuoGroupName;
                await _wexTelGroupRepository.AddAsync(telGroup, HttpContext.RequestAborted);
            }
            else
            {
                //修改
                var telGroup = await _wexTelGroupRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
                if (telGroup is null)
                {
                    throw UserFriendlyException.SameMessage("未找到对应数据，无法修改");
                }
                if (dto.GroupId == 0)
                {
                    await _wexTelGroupRepository.RemoveAsync(dto.Id, false, HttpContext.RequestAborted);
                }
                else
                {
                    telGroup.GroupId = dto.GroupId;
                    telGroup.GroupName = dto.GroupName;
                    telGroup.ZuoGroupName = dto.ZuoGroupName;
                    await _wexTelGroupRepository.UpdateAsync(telGroup);
                }
            }
        }

        /// <summary>
        /// 分机组列表
        /// </summary>
        /// <returns></returns>
        [HttpGet("telgroup-list")]
        public async Task<List<WexTelGroupDto>> TelGroupList()
        {
            var rsp = await _wexClient.QueryGroupAsync(new QueryGroupRequest() { }, HttpContext.RequestAborted);
            var groupList = rsp.Data;
            var list = _mapper.Map<List<WexTelGroupDto>>(groupList);
            return list;
        }

        /// <summary>
        /// 根据分机号查询分机关联坐席组
        /// </summary>
        /// <param name="telno"></param>
        /// <returns></returns>
        [HttpGet("telgroup/{telno}")]
        public async Task<WexTelGroup> TelGroup(string telno)
        {
            var telGroup = await _wexTelGroupRepository.GetAsync(x => x.TelNo == telno, HttpContext.RequestAborted);
            if (telGroup is null)
            {
                return new WexTelGroup() { TelNo = telno, };
            }
            return telGroup;
        }

        #endregion
    }
}
