using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;

using FMSAdmin.Data;
using FMSAdmin.Helpers;
using FMSAdmin.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using FMSAdmin.Models;
using System.Transactions;

namespace FMSApp.Repositories {
    public class WorkRequestRepository {
        private readonly ILogger<WorkRequestRepository> _logger;
        private readonly FMSContext _context;

        private readonly AppSettings _appSettings;
        private readonly StorageHelper _storage;

        private readonly WorkConfigRepository _workConfigRepository;

        private readonly HolidayRepository _holidayRepository;

        public WorkRequestRepository(
            ILogger<WorkRequestRepository> logger,
            FMSContext context,
            IOptions<AppSettings> appSettings,
            StorageHelper storage,
            WorkConfigRepository workConfigRepository,
            HolidayRepository holidayRepository
            ) {
            _logger = logger;
            _context = context;
            _appSettings = appSettings.Value;
            _storage = storage;
            _workConfigRepository = workConfigRepository;
            _holidayRepository = holidayRepository;
        }

        public void CreateFromWorkSchedules() {

            var today = DateTime.Now.Date;
            var seekStartDate = today.AddDays(-31);

            using (var trans = new TransactionScope()) {
                var query = from schedule in _context.FmsWorkSchedule
                            where schedule.IsUse == true
                            && schedule.WorkDueDate >= seekStartDate
                            && _context.FmsWorkRequest.Where(
                                request => request.SiteId == schedule.SiteId
                                && request.WorkScheduleId == schedule.WorkScheduleId
                                && request.StartWorkDate == schedule.WorkDueDate
                            ).Any() == false
                            select schedule;

                foreach (var schedule in query) {
                    CreateFromWorkSchedule(schedule);
                }

                trans.Complete();
            }
        }

        public bool IsCreatedRequest(FmsWorkSchedule schedule) {
            return _context.FmsWorkRequest.Any(
                x => x.SiteId == schedule.SiteId
                && x.WorkScheduleId == schedule.WorkScheduleId
                && x.StartWorkDate == schedule.WorkDueDate
            );
        }

        public void CreateAllRequestFromWorkSchedule(FmsWorkSchedule schedule, bool bTran = false) {
            var today = DateTime.Now.Date;
            TransactionScope trans = null;
            if (bTran) trans = new TransactionScope();
            try {
                if (schedule.WorkExpireDate == null) throw new ServiceException("작업만료일을 선택하세요.");

                DateTime startWorkDate = schedule.WorkDueDate;
                DateTime? endWorkDate = schedule.WorkEndDate;

                DateTime expireDate = schedule.WorkExpireDate.Value;
                var weekend = _holidayRepository.GetHolidayWeekend(schedule.SiteId);
                var holidays = _holidayRepository.GetHolidays(schedule.SiteId);
                var workrequestFileCategory = _context.CmFileCategory.First(x => x.Name == "workrequest");
                var workscheduleFileCategory = _context.CmFileCategory.First(x => x.Name == "workschedule");

                if (schedule.CmFile != null && schedule.CmFile.IsUpload) {
                    _logger.LogError("<<<<<<<<<<<<<<<<<< schedule.CmFile IS NOT NULL >>>>>>>>>>>>>>>>");
                    schedule.CmFile.SiteId = schedule.SiteId;
                    schedule.CmFile.FileCategoryId = workscheduleFileCategory.FileCategoryId;
                    schedule.CmFile.FileCategory = workscheduleFileCategory;
                    schedule.CmFile.CreatedDate = DateTime.Now;
                } else {
                    schedule.CmFile = null;
                }
                schedule.IsNotProcess = true;
                schedule.IsUse = true;

                _context.FmsWorkSchedule.Add(schedule);
                _context.SaveChanges();

                while (true) {
                    var request = new FmsWorkRequest();
                    request.CmFile = schedule.CmFile != null ? (new CmFile() {
                        SiteId = schedule.CmFile.SiteId,
                        FileCategoryId = workrequestFileCategory.FileCategoryId,
                        FileCategory = workrequestFileCategory,
                        Name = schedule.CmFile.Name,
                        FileSize = schedule.CmFile.FileSize,
                        CreatedDate = schedule.CmFile.CreatedDate,
                        ContentType = schedule.CmFile.ContentType,
                        IsUpload = schedule.CmFile.IsUpload,
                        IsDelete = schedule.CmFile.IsDelete,
                        Url = schedule.CmFile.Url,
                        Path = schedule.CmFile.Path
                    }) : null;
                    request.FmsWorkSchedule = schedule;
                    request.SiteId = schedule.SiteId;
                    request.WorkTypeId = schedule.WorkTypeId;
                    request.BusinessFieldId = schedule.BusinessFieldId;
                    request.WorkProgressId = 1; // 작업계획
                    request.Title = schedule.Name;
                    request.Content = schedule.Content;
                    request.RequestUserId = schedule.UpdateUserId;
                    request.RequestDate = today;
                    request.InspectionAgencyId = schedule.InspectionAgencyId;
                    request.StartWorkDate = startWorkDate;
                    request.WorkEndDate = endWorkDate;
                    request.WorkPartnerId = schedule.WorkPartnerId;
                    request.WorkPrice = schedule.WorkPrice;

                    foreach (var facility in schedule.FmsWorkScheduleToFacility) {
                        request.FmsWorkRequestToFacility.Add(new FmsWorkRequestToFacility() {
                            SiteId = schedule.SiteId,
                            FacilityCode = facility.FacilityCode
                        });
                    }

                    _context.FmsWorkRequest.Add(request);
                    _context.SaveChanges();

                    // 자동지시 (일자가 도래해야 자동지시처리.)
                    if (_workConfigRepository.IsAutoOrder(true) || schedule.IsAutoOrder) {
                        if (request.StartWorkDate.Date <= today) {
                            this.WorkToOrder(request.SiteId, request.WorkRequestId, request.RequestUserId, "");
                        }
                    }

                    if (request.CmFile != null && request.CmFile.IsUpload) {
                        _storage.CopyEntity(request.CmFile.Path, request.CmFile);
                    }

                    startWorkDate = this.GetNextDueDate(schedule, startWorkDate, weekend, holidays);
                    if (!Util.IsNullOrEmpty(endWorkDate)) endWorkDate = this.GetNextDueDate(schedule, endWorkDate.Value, weekend, holidays);
                    if (startWorkDate > expireDate) {
                        break;
                    }
                }

                if (schedule.CmFile != null && schedule.CmFile.IsUpload) {
                    _storage.CopyEntity(schedule.CmFile.Path, schedule.CmFile);
                }

                if (trans != null) trans.Complete();
            } finally {
                if (trans != null) trans.Dispose();
            }
        }

        public void CreateFromWorkSchedule(FmsWorkSchedule schedule, bool bTran = false) {
            if (schedule.IsNotProcess) return;

            var today = DateTime.Now.Date;
            TransactionScope trans = null;
            if (bTran) trans = new TransactionScope();
            try {
                var request = new FmsWorkRequest();
                request.SiteId = schedule.SiteId;
                request.WorkTypeId = schedule.WorkTypeId;
                request.BusinessFieldId = schedule.BusinessFieldId;
                request.WorkScheduleId = schedule.WorkScheduleId;
                request.WorkProgressId = 1; // 작업계획
                request.Title = schedule.Name;
                request.Content = schedule.Content;
                request.RequestUserId = schedule.UpdateUserId;
                request.RequestDate = today;
                request.InspectionAgencyId = schedule.InspectionAgencyId;
                request.StartWorkDate = schedule.WorkDueDate;
                request.WorkPartnerName = schedule.WorkPartnerName;
                request.WorkPrice = schedule.WorkPrice;
                //request.WorkItem = schedule.WorkItem;

                foreach (var facility in schedule.FmsWorkScheduleToFacility) {
                    request.FmsWorkRequestToFacility.Add(new FmsWorkRequestToFacility() {
                        SiteId = schedule.SiteId,
                        FacilityCode = facility.FacilityCode
                    });
                }

                _context.FmsWorkRequest.Add(request);
                _context.SaveChanges();

                // 자동지시 (일자가 도래해야 자동지시처리.)
                if (_workConfigRepository.IsAutoOrder(true) || schedule.IsAutoOrder) {
                    if (request.StartWorkDate.Date <= today) {
                        this.WorkToOrder(request.SiteId, request.WorkRequestId, request.RequestUserId, "");
                    }
                }

                if (trans != null) trans.Complete();
            } finally {
                if (trans != null) trans.Dispose();
            }
        }

        public void UpdateFromWorkSchedule(FmsWorkSchedule schedule) {
            // 작업계획(1) : 수정한다.
            // 작업반려(2) : 수정하지 않는다.
            // 작업대기(3) : 수정한다.
            // 작업진행(4) : 수정한다.
            // 작업완료(5) : 수정시 오류메시지 표시한다.
            // 작업승인(6) : 수정하지 않는다. 
            //              (승인시 차기작업이 생성되었을것이므로, 생성된 작업계획~작업진행 상태의 데이터에 대한 수정이 가해질 것임)
            //              (차기작업을 생성하지 않았을경우 수정할 데이터가 없다.)
            // 작업보류(7) : 수정한다.
            var request = _context.FmsWorkRequest.Where(
                x => x.SiteId == schedule.SiteId
                && x.WorkScheduleId == schedule.WorkScheduleId
                && (new int[] { 1, 3, 4, 7 }).Contains(x.WorkProgressId)
            ).OrderByDescending(x => x.WorkRequestId).FirstOrDefault();
            if (request == null) {
                var completeCount = _context.FmsWorkRequest.Where(
                    x => x.SiteId == schedule.SiteId
                    && x.WorkScheduleId == schedule.WorkScheduleId
                    && x.WorkProgressId == 5
                ).Count();
                if (completeCount > 0) {
                    throw new ServiceException("작업완료처리된 작업은 계획수정이 불가능합니다. 작업승인 이후에 계획 수정이 가능합니다.");
                } else {
                    // 계획, 대기, 진행, 완료, 보류 건이 없는상태
                    // 작업이력관리에서 해당 계획의 작업이 삭제된 경우이다.
                    // 이 경우 기존 계획으로 새로 생성해야한다.
                    CreateFromWorkSchedule(schedule);
                }

                return;
            }

            // Request 정보 수정
            request.Title = schedule.Name;
            request.StartWorkDate = schedule.WorkDueDate.Date;
            request.InspectionAgencyId = schedule.InspectionAgencyId;
            request.Content = schedule.Content;
            //request.RequestUserId = schedule.UpdateUserId;
            //request.RequestDate = today;
            _context.FmsWorkRequest.Update(request);
            _context.SaveChanges();

            request.FmsWorkRequestToFacility.Clear();
            _context.SaveChanges();
            foreach (var facility in schedule.FmsWorkScheduleToFacility) {
                _context.FmsWorkRequestToFacility.Add(new FmsWorkRequestToFacility() {
                    SiteId = facility.SiteId,
                    WorkRequestId = request.WorkRequestId,
                    FacilityCode = facility.FacilityCode
                });
            }
            _context.SaveChanges();

            // Order 정보 수정
            var order = request.FmsWorkOrder;
            if (order != null) {
                order.Title = request.Title;
                order.StartWorkDate = request.StartWorkDate;
                order.Content = request.Content;
                _context.FmsWorkOrder.Update(order);
                _context.SaveChanges();

                order.FmsWorkOrderToFacility.Clear();
                _context.SaveChanges();
                foreach (var facility in schedule.FmsWorkScheduleToFacility) {
                    _context.FmsWorkOrderToFacility.Add(new FmsWorkOrderToFacility() {
                        SiteId = facility.SiteId,
                        WorkRequestId = request.WorkRequestId,
                        FacilityCode = facility.FacilityCode
                    });
                }
                _context.SaveChanges();
            }

            // Result 정보 수정
            var result = request.FmsWorkResult;
            if (result != null) {
                result.StartDate = request.StartWorkDate;
                result.EndDate = request.StartWorkDate;
                result.Content = request.Content;
                _context.FmsWorkOrder.Update(order);
                _context.SaveChanges();

                result.FmsWorkResultToFacility.Clear();
                _context.SaveChanges();
                foreach (var facility in schedule.FmsWorkScheduleToFacility) {
                    _context.FmsWorkResultToFacility.Add(new FmsWorkResultToFacility() {
                        SiteId = facility.SiteId,
                        WorkRequestId = request.WorkRequestId,
                        FacilityCode = facility.FacilityCode
                    });
                }
                _context.SaveChanges();
            }
        }

        // 자동지시
        public void WorkToOrders() {
            _logger.LogInformation($"=============================================================================================");
            _logger.LogInformation($"작업시작일이 지나거나 당일인 실행계획 데이터를 작업지시 처리 (처리일 : {DateTime.Now})");

            int processCount = 0;
            if (_workConfigRepository.IsAutoOrder(true)) {
                _logger.LogInformation($"자동지시 전역설정 TRUE");
                // 전역설정이 자동지시로 되어있을때는 모든 실행계획 자동지시(정기,법정,교육,수시,공사)
                var query = from request in _context.FmsWorkRequest
                            where request.StartWorkDate < DateTime.Now.AddDays(1).Date
                            && request.WorkProgressId == 1
                            && _context.FmsWorkOrder.Where(x => x.SiteId == request.SiteId
                                                        && x.WorkRequestId == request.WorkRequestId
                                                        && x.Title == request.Title).Any() == false
                            select request;
                foreach (var request in query.ToList()) {
                    WorkToOrder(request.SiteId, request.WorkRequestId, request.RequestUserId, "", true);
                    processCount++;
                }
            } else {
                _logger.LogInformation($"자동지시 전역설정 FALSE");
                // 전역설정이 자동지시로 되어있지 않을때는 정기점검,법정검사,법정교육 (FmsWorkSchedule 등록되어있는 실행계획) 만 자동지시
                // 기초계획에 자동지시여부가 true 인것만 자동지시
                var query = from schedule in _context.FmsWorkSchedule
                            join request in _context.FmsWorkRequest on schedule.WorkScheduleId equals request.WorkScheduleId
                            where schedule.IsUse == true
                            && request.WorkProgressId == 1
                            && schedule.IsAutoOrder == true
                            && (request.StartWorkDate < DateTime.Now.AddDays(1).Date)
                            && _context.FmsWorkOrder.Where(x => x.SiteId == request.SiteId
                                                        && x.WorkRequestId == request.WorkRequestId
                                                        && x.Title == request.Title).Any() == false
                            select request;
                foreach (var request in query.ToList()) {
                    WorkToOrder(request.SiteId, request.WorkRequestId, request.RequestUserId, "", true);
                    processCount++;
                }
            }

            _logger.LogInformation($"처리완료 : {processCount}건");
            _logger.LogInformation($"=============================================================================================");
        }

        // 자동수행
        public void WorkToResults() {
            _logger.LogInformation($"=============================================================================================");
            _logger.LogInformation($"작업수행 처리되지 않은 작업대기 목록 자동수행  (처리일시 : {DateTime.Now})");

            int processCount = 0;
            if (_workConfigRepository.IsAutoResult(true)) {
                _logger.LogInformation($"자동수행 전역설정 TRUE");
                var query = from order in _context.FmsWorkOrder
                            join request in _context.FmsWorkRequest on order.WorkRequestId equals request.WorkRequestId
                            where request.WorkProgressId == 3
                                && _context.FmsWorkResult.Where(x => x.SiteId == order.SiteId
                                                            && x.WorkRequestId == order.WorkRequestId).Any() == false
                            select order;
                foreach (var order in query.ToList()) {
                    WorkToResult(order, new FmsWorkResult {
                        StartDate = order.StartWorkDate.Date,
                        EndDate = order.StartWorkDate.Date,
                        Content = "",
                        WorkerUserId = order.OrderUserId,
                    }, true);
                    processCount++;
                }
            } else {
                _logger.LogInformation($"자동수행 전역설정 FALSE => 자동수행 안함.");
            }

            _logger.LogInformation($"처리완료 : {processCount}건");
            _logger.LogInformation($"=============================================================================================");
        }

        public void WorkToOrder(int siteId, int workRequestId, string orderUserId, string orderContent, bool bTran = false) {
            TransactionScope trans = null;
            if (bTran) trans = new TransactionScope();
            try {

                var persist = _context.FmsWorkRequest.FirstOrDefault(
                    x => x.SiteId == siteId
                    && x.WorkRequestId == workRequestId
                );
                if (persist == null) throw new ServiceException("존재하지 않는 데이터 입니다.");

                if (persist.WorkProgressId == 2) {
                    throw new ServiceException("반려된 작업은 작업지시 처리할 수 없습니다.");
                }
                if (persist.WorkProgressId != 1) {
                    throw new ServiceException("이미 작업지시 처리된 데이터입니다.");
                }

                // WorkRequest.WorkProgressId = 3
                persist.WorkProgressId = 3;
                _context.FmsWorkRequest.Update(persist);
                _context.SaveChanges();

                // WorkOrder 생성
                var order = new FmsWorkOrder();
                order.SiteId = persist.SiteId;
                order.WorkRequestId = persist.WorkRequestId;
                order.BusinessFieldId = persist.BusinessFieldId;
                order.Title = persist.Title;
                order.StartWorkDate = persist.StartWorkDate;
                order.OrderUserId = orderUserId;
                order.OrderDate = DateTime.Now;
                order.Content = orderContent;

                _context.FmsWorkOrder.Add(order);
                _context.SaveChanges();

                // 기존 WorkOrderToFacility 삭제
                var facilities = _context.FmsWorkOrderToFacility.Where(
                    x => x.SiteId == persist.SiteId
                    && x.WorkRequestId == persist.WorkRequestId
                );
                _context.FmsWorkOrderToFacility.RemoveRange(facilities);
                _context.SaveChanges();

                // 신규 WorkOrderToFacility 생성 From FmsWorkRequestToFacility
                foreach (var facility in persist.FmsWorkRequestToFacility.ToList()) {
                    _context.FmsWorkOrderToFacility.Add(new FmsWorkOrderToFacility() {
                        SiteId = facility.SiteId,
                        WorkRequestId = facility.WorkRequestId,
                        FacilityCode = facility.FacilityCode,
                    });
                }
                _context.SaveChanges();

                // 자동진행
                if (_workConfigRepository.IsAutoResult(true)) {
                    this.WorkToResult(order, new FmsWorkResult() {
                        StartDate = order.StartWorkDate.Date,
                        EndDate = order.StartWorkDate.Date,
                        Content = "",
                        WorkerUserId = order.OrderUserId,
                    });
                }

                if (trans != null) trans.Complete();
            } catch (Exception ex) {
                _logger.LogError(ex, "");
                throw ex;
            } finally {
                if (trans != null) trans.Dispose();
            }
        }

        public void WorkToResult(FmsWorkOrder order, FmsWorkResult data, bool bTran = false) {
            TransactionScope trans = null;
            if (bTran) trans = new TransactionScope();
            try {
                if (order.FmsWorkRequest.WorkProgressId != 3) {
                    throw new ServiceException("작업 대기상태의 데이터만 작업진행 가능합니다.");
                }

                // WorkRequest 의 ProgressId 변경
                order.FmsWorkRequest.WorkProgressId = 4;
                _context.FmsWorkRequest.Update(order.FmsWorkRequest);
                _context.SaveChanges();

                // WorkOrder 의 StartWorkDate 변경
                order.StartWorkDate = data.StartDate.Date;
                _context.FmsWorkOrder.Update(order);
                _context.SaveChanges();

                // WorkResult 생성
                var result = new FmsWorkResult() {
                    SiteId = order.SiteId,
                    WorkRequestId = order.WorkRequestId,
                    StartDate = data.StartDate.Date,
                    EndDate = data.EndDate.Date,
                    Content = data.Content,
                    IsConfirmed = false,
                    WorkerUserId = data.WorkerUserId,
                };
                _context.FmsWorkResult.Add(result);
                _context.SaveChanges();

                // 기존 FmsWorkResultToFacility 삭제
                var facilities = _context.FmsWorkResultToFacility.Where(
                    x => x.SiteId == order.SiteId
                    && x.WorkRequestId == order.WorkRequestId
                );
                _context.FmsWorkResultToFacility.RemoveRange(facilities);
                _context.SaveChanges();

                // 신규 FmsWorkResultToFacility 생성 From FmsWorkOrderToFacility
                foreach (var facility in order.FmsWorkOrderToFacility.ToList()) {
                    _context.FmsWorkResultToFacility.Add(new FmsWorkResultToFacility() {
                        SiteId = facility.SiteId,
                        WorkRequestId = facility.WorkRequestId,
                        FacilityCode = facility.FacilityCode,
                    });
                }
                _context.SaveChanges();

                if (trans != null) trans.Complete();
            } catch (Exception ex) {
                _logger.LogError(ex, "");
                throw ex;
            } finally {
                if (trans != null) trans.Dispose();
            }
        }

        public void WorkToResult(int siteId, int workRequestId, FmsWorkResult data, bool bTran = false) {
            TransactionScope trans = null;
            if (bTran) trans = new TransactionScope();
            try {
                var persist = _context.FmsWorkOrder.FirstOrDefault(
                    x => x.SiteId == siteId
                    && x.WorkRequestId == workRequestId
                );
                if (persist == null) throw new ServiceException("존재하지 않는 데이터 입니다.");

                this.WorkToResult(persist, data);

                if (trans != null) trans.Complete();
            } finally {
                if (trans != null) trans.Dispose();
            }
        }

        public DateTime GetNextDueDate(FmsWorkSchedule schedule, DateTime date, CmHolidayWeekend weekend, IList<DateTime> holidays) {
            DateTime WorkDueDate = date;
            DateTime EndDate = date;

            switch (schedule.CycleUnitId) {
                case 1: WorkDueDate = EndDate.AddDays(schedule.CycleSize); break;
                case 2: WorkDueDate = EndDate.AddDays(schedule.CycleSize * 7); break;
                case 3: WorkDueDate = EndDate.AddMonths(schedule.CycleSize); break;
                case 4: WorkDueDate = EndDate.AddMonths(schedule.CycleSize * 3); break;
                case 5: WorkDueDate = EndDate.AddMonths(schedule.CycleSize * 6); break;
                case 6: WorkDueDate = EndDate.AddYears(schedule.CycleSize); break;
            }

            if (schedule.HolidayWorkTypeId == 0) return WorkDueDate;

            bool isHoliday = _holidayRepository.IsHoliday(WorkDueDate, weekend, holidays);
            if (schedule.HolidayWorkTypeId == 1) {
                while (!isHoliday) {
                    WorkDueDate = WorkDueDate.AddDays(1);
                    isHoliday = _holidayRepository.IsHoliday(WorkDueDate, weekend, holidays);
                }
                return WorkDueDate;
            }
            if (schedule.HolidayWorkTypeId == 2) {
                while (isHoliday) {
                    WorkDueDate = WorkDueDate.AddDays(1);
                    isHoliday = _holidayRepository.IsHoliday(WorkDueDate, weekend, holidays);
                }
                return WorkDueDate;
            }
            return WorkDueDate;
        }

    }
}