using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Fab2ApprovalSystem.Models;
using Fab2ApprovalSystem.DMO;
using Fab2ApprovalSystem.Misc;
using Kendo.Mvc.Extensions;
using Kendo.Mvc.UI;

namespace Fab2ApprovalSystem.Controllers
{
    [Authorize]
    [OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
    [SessionExpireFilter]
    public class PartsRequestController : Controller
    {
        PartsRequestDMO prDMO = new PartsRequestDMO();
        UserAccountDMO userDMO = new UserAccountDMO();
        WorkflowDMO wfDMO = new WorkflowDMO();

        const int WorkflowNumber = 1;

        public PartsRequestController()
        {
            ViewBag.ShowReAssignApprovers = false;
        }

        protected ActionResult HandleValidationError(string msg)
        {
            Response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
            return Json(new { result = "Invalid", detail = msg });
        }

        protected ActionResult HandleAPIException(int issueID, Exception ex, string additionalKeys = "")
        {
            HandleException(issueID, ex, additionalKeys);

            Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
            return Json(new { result = "Error", issueID = issueID, detail = ex.Message });
        }

        protected void HandleException(int issueID, Exception ex, string additionalKeys = "")
        {
            var st = new System.Diagnostics.StackTrace();
            var sf = st.GetFrame(1);

            String controller = sf.GetMethod().DeclaringType.Name.Replace("Controller", "");
            String method = sf.GetMethod().Name;

            string detailedException = String.Format(
                "Exception for issue # {0}\r\n" + 
                "Controller: {1}, Method: {2}, User: {3}, Keys: {4}\r\n" + 
                "=====\r\n", 
                issueID, 
                controller,
                method,
                User?.Identity?.Name,
                additionalKeys);

            Exception x = ex;
            while (x != null)
            {
                detailedException += x.ToString();
                detailedException += "\r\n=====\r\n";
                x = x.InnerException;
            }

            Functions.WriteEvent(detailedException, System.Diagnostics.EventLogEntryType.Error);

            EventLogDMO.Add(new WinEventLog()
            {
                IssueID = issueID,
                UserID = @User.Identity.Name,
                DocumentType = controller,
                OperationType = "Error",
                Comments = detailedException
            });

        }

        // GET: PartsRequest
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult Edit(int issueID)
        {
            var pr = new PartsRequest();
            try
            {
                pr = prDMO.Get(issueID);

                if (pr == null)
                {
                    ViewBag.ErrorDescription = "Document does not exist";
                    return View("Error");
                }
                else if (pr.CurrentStep < 0)
                {
                    ViewBag.ErrorDescription = "Document is deleted";
                    return View("Error");
                }
                else if (pr.CurrentStep >= 1)
                {
                    return RedirectToAction("EditApproval", new { issueID = issueID });
                }
                else
                {
                    if (pr.OriginatorID != (int)Session[GlobalVars.SESSION_USERID])
                    {
                        if (Convert.ToBoolean(Session[GlobalVars.IS_ADMIN]) == false)
                        {
                            return RedirectToAction("ReadOnly", new { issueID = issueID });
                        }
                    }

                    ViewBag.UserList = userDMO.GetAllUsers();
                    return View(pr);
                }
            }
            catch (Exception e)
            {
                HandleException(issueID, e);
                return View("Error");
            }
        }

        [HttpPost]
        public ActionResult Edit(PartsRequest pr)
        {
            try
            {
                var pr_srv  = prDMO.Get(pr.PRNumber);

                if (pr_srv == null)
                {
                    return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Document does not exist");
                }
                if (pr_srv.CurrentStep < 0)
                {
                    return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Document is deleted");
                }

                if (pr_srv.CurrentStep >= 1)
                {
                    return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Parts Request is not editable");
                }

                prDMO.Update(pr);
                return Content("");
            }
            catch (Exception e)
            {
                return HandleAPIException(pr.PRNumber, e);
            }
        }

        public ActionResult EditApproval(int issueID)
        {
            var pr = new PartsRequest();
            try
            {
                int myUserID = (int)Session[GlobalVars.SESSION_USERID];

                pr = prDMO.Get(issueID);

                if (pr == null)
                {
                    ViewBag.ErrorDescription = "Document does not exist";
                    return View("Error");
                }
                if (pr.CurrentStep < 0)
                {
                    ViewBag.ErrorDescription = "Document is deleted";
                    return View("Error");
                }

                var wfStep = wfDMO.GetWorkflowStep((int)GlobalVars.DocumentType.PartsRequest, WorkflowNumber, pr.CurrentStep);

                var userList = MiscDMO.GetPendingApproversListByDocument(issueID, Convert.ToByte(pr.CurrentStep), (int)GlobalVars.DocumentType.PartsRequest);
                ViewBag.IsApprover = (userList.Count(u => u.UserID == myUserID) > 0);

                if (ViewBag.IsApprover == false)
                {
                    if (pr.OriginatorID != myUserID)
                    {
                        if (Convert.ToBoolean(Session[GlobalVars.IS_ADMIN]) == false)
                        {
                            return RedirectToAction("ReadOnly", new { issueID = issueID });
                        }
                    }
                }

                ViewBag.IsAdmin = Convert.ToBoolean(Session[GlobalVars.IS_ADMIN]);
                ViewBag.IsOriginator = (pr.OriginatorID == myUserID);
                ViewBag.AllowReject = (wfStep != null ? (wfStep.AllowReject.HasValue && wfStep.AllowReject.Value) : false);
                ViewBag.ShowReAssignApprovers = ViewBag.IsAdmin || ViewBag.IsOriginator;
                ViewBag.ShowAddApprovers = ViewBag.IsAdmin || ViewBag.IsApprover || ViewBag.IsOriginator;
                ViewBag.UserList = userDMO.GetAllUsers();
                return View(pr);
            }
            catch (Exception e)
            {
                HandleException(issueID, e);
                return View("Error");
            }
        }

        public ActionResult ReadOnly(int issueID)
        {
            var pr = new PartsRequest();
            try
            {
                int myUserID = (int)Session[GlobalVars.SESSION_USERID];

                pr = prDMO.Get(issueID);

                if (pr == null)
                {
                    ViewBag.ErrorDescription = "Document does not exist";
                    return View("Error");
                }
                if (pr.CurrentStep < 0)
                {
                    ViewBag.ErrorDescription = "Document is deleted";
                    return View("Error");
                }

                ViewBag.IsAdmin = Convert.ToBoolean(Session[GlobalVars.IS_ADMIN]);
                ViewBag.IsOriginator = (pr.OriginatorID == myUserID);
                ViewBag.UserList = userDMO.GetAllUsers();
                return View(pr);
            }
            catch (Exception e)
            {
                HandleException(issueID, e);
                return View("Error");
            }
        }

        public ActionResult Create()
        {
            var pr = new PartsRequest();
            try
            {
                pr.OriginatorID = (int)Session[GlobalVars.SESSION_USERID];

                if (!CanCreatePartsRequest(pr.OriginatorID) && Convert.ToBoolean(Session[GlobalVars.CAN_CREATE_PARTS_REQUEST]) == false)
                    throw new Exception("User does not have permission to create Parts Request");

                prDMO.Insert(pr);

                return RedirectToAction("Edit", new { issueID = pr.PRNumber });
            }
            catch (Exception e)
            {
                return HandleAPIException(pr.PRNumber, e);
            }
        }

        public static bool CanCreatePartsRequest(int userID)
        {
            var adminDMO = new AdminDMO();
            var role = adminDMO.GetSubRoles().Where(r => string.Equals(r.RoleName, "Parts Request", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
            if (role != null)
            {
                var subrole = role.SubRoles.FirstOrDefault(sr => string.Equals(sr.SubRoleCategoryItem, "Originator", StringComparison.OrdinalIgnoreCase));
                if (subrole != null)
                {
                    var users = adminDMO.GetAllUsersBySubRole(subrole.SubRoleID);
                    if (users.Count(u => u.UserID == userID) > 0)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        public ActionResult Attachment_Read([DataSourceRequest]DataSourceRequest request, int prNumber)
        {
            try
            {
                return Json(prDMO.GetAttachments(prNumber).ToDataSourceResult(request), JsonRequestBehavior.AllowGet);
            }
            catch (Exception ex)
            {
                return HandleAPIException(prNumber, ex);
            }
        }

        [HttpPost]
        public ActionResult AttachSave(IEnumerable<HttpPostedFileBase> files, int prNumber)
        {
            // The Name of the Upload component is "files"
            if (files != null)
            {
                foreach (var file in files)
                {
                    // Some browsers send file names with full path.
                    // We are only interested in the file name.
                    var fileName = System.IO.Path.GetFileName(file.FileName);

                    string prFolderPath = Functions.GetAttachmentFolder() + "PartsRequest\\" + prNumber.ToString();

                    var di = new System.IO.DirectoryInfo(prFolderPath);
                    if (!di.Exists)
                        di.Create();

                    var physicalPath = System.IO.Path.Combine(prFolderPath, fileName);

                    file.SaveAs(physicalPath);
                    var attach = new PartsRequestAttachment()
                    {
                        PRNumber = prNumber,
                        FileName = fileName,
                        UserID = (int)Session[GlobalVars.SESSION_USERID],
                    };
                    prDMO.InsertAttachment(attach);
                }
            }

            return Content("");
        }

        public FileResult DownloadFile(string attachmentID, string prNumber)
        {
            string fileName = prDMO.GetFileName(attachmentID);

            string folderPath = Functions.GetAttachmentFolder() + "PartsRequest\\" + prNumber.ToString();
            var sDocument = System.IO.Path.Combine(folderPath, fileName);

            var FDir_AppData = Functions.GetAttachmentFolder();
            if (!sDocument.StartsWith(FDir_AppData))
            {
                // Ensure that we are serving file only inside the Fab2ApprovalAttachments folder
                // and block requests outside like "../web.config"
                throw new HttpException(403, "Forbidden");
            }

            if (!System.IO.File.Exists(sDocument))
                return null;

            return File(sDocument, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
        }

        [HttpPost]
        public ActionResult DeleteAttachment(int attachmentID, string fileName, int prNumber)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    prDMO.DeleteAttachment(attachmentID);
                    var physicalPath = System.IO.Path.Combine(Functions.GetAttachmentFolder() + @"PartsRequest\" + prNumber.ToString(), fileName);

                    if (System.IO.File.Exists(physicalPath))
                    {
                        System.IO.File.Delete(physicalPath);
                    }
                }
                return Content("");
            }
            catch (Exception e)
            {
                return HandleAPIException(prNumber, e, "AttachmentID=" + attachmentID.ToString());
            }
        }

        [HttpPost]
        public ActionResult Submit(int prNumber)
        {
            try
            {
                int myUserID = (int)Session[GlobalVars.SESSION_USERID];

                var pr = prDMO.Get(prNumber);

                if (pr == null)
                    return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Document does not exist");

                if (String.IsNullOrWhiteSpace(pr.Title))
                    return HandleValidationError("Title is required");
                if (pr.RequestorID <= 0)
                    return HandleValidationError("Requestor is required");
                if (pr.TechLeadID <= 0)
                    return HandleValidationError("Tech Lead is required");

                prDMO.Submit(prNumber, myUserID);

                var approvers = MiscDMO.GetApprovalsByDocument(prNumber, (int)GlobalVars.DocumentType.PartsRequest);

                if (approvers.Count(a => a.Step.HasValue && a.Step.Value == 1 && a.UserID == myUserID) > 0)
                {
                    // Auto-Approve if the user is an approver for workflow step 1
                    var c = Approve(prNumber, 1, "Auto-Approve");
                    if (c != null && c is ContentResult)
                    {
                        var result = ((ContentResult)c).Content;
                        if (!String.Equals(result, "OK"))
                            throw new Exception(result);
                    }
                    if (c != null && c is JsonResult)
                        return c;
                }
                else
                {
                    // Do step 1 notification
                    NotifyApprovers(prNumber, 1);
                }

                if (Request.IsAjaxRequest())
                {
                    return Content("Redirect");
                }
                else
                {
                    return Content("Invalid");
                }
                
            }
            catch (Exception e)
            {
                return HandleAPIException(prNumber, e);
            }
        }

        public ActionResult GetApproversList([DataSourceRequest]DataSourceRequest request, int issueID, byte step)
        {
            try
            {
                return Json(MiscDMO.GetApproversListByDocument(issueID, step, (int)GlobalVars.DocumentType.PartsRequest).ToDataSourceResult(request));
            }
            catch (Exception e)
            {
                return HandleAPIException(issueID, e, "Step=" + step.ToString());
            }
        }

        public ActionResult GetAllUsersList()
        {
            try
            {
                UserAccountDMO userDMO = new UserAccountDMO();
                IEnumerable<LoginModel> userlist = userDMO.GetAllUsers();
                return Json(userlist, JsonRequestBehavior.AllowGet);
            }
            catch (Exception e)
            {
                return HandleAPIException(0, e);
            }
        }

        [HttpPost]
        public ActionResult ReAssignApproval(int issueID, int fromUserID, int userIDs, byte step)
        {
            try
            {
                String email = wfDMO.ReAssignApproval(
                    issueID, fromUserID, userIDs, step, (int)GlobalVars.DocumentType.PartsRequest);

                NotifyReAssignment(issueID, email);

                return Content("OK");
            }
            catch (Exception e)
            {
                return HandleAPIException(issueID, e);
            }
        }

        [HttpPost]
        public ActionResult Approve(int prNumber, byte currentStep, string comments)
        {
            try
            {
                bool lastStep = false;
                bool lastApproverInCurrentStep = false;
                int myUserID = (int)Session[GlobalVars.SESSION_USERID];

                var pr = prDMO.Get(prNumber);

                if (pr == null)
                    return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Document does not exist");

                while (true)
                {

                    lastApproverInCurrentStep = wfDMO.Approve(
                        prNumber, currentStep, comments, out lastStep,
                        (int)Session[GlobalVars.SESSION_USERID],
                        (int)GlobalVars.DocumentType.PartsRequest,
                        WorkflowNumber);

                    if (!lastApproverInCurrentStep) break;
                    if (lastStep)
                    {
                        NotifyCompletion(prNumber);
                        break;
                    }

                    currentStep++;

                    var approvers = MiscDMO.GetApprovalsByDocument(prNumber, (int)GlobalVars.DocumentType.PartsRequest);
                    if (approvers.Count(a => a.Step.HasValue && a.Step.Value == currentStep) == 0)
                        return Content("No approvers found for next step, contact support!");

                    // only continue with approving if the next step has me as an approver also
                    if (approvers.Count(a => a.Step.HasValue && a.Step.Value == currentStep && a.UserID == myUserID) == 0)
                    {
                        NotifyApprovers(prNumber, currentStep);
                        break;
                    }
                }

                return Content("OK");
            }
            catch (Exception e)
            {
                return HandleAPIException(prNumber, e);
            }
        }

        protected void SendEmailNotification(String subject, int prNumber, string toEmail, string emailTemplate)
        {

            string senderName = "Parts Request";

            EmailNotification en = new EmailNotification(subject, System.Configuration.ConfigurationManager.AppSettings["EmailTemplatesPath"]);
            string[] emailparams = new string[5];
            emailparams[0] = prNumber.ToString();
            emailparams[1] = prNumber.ToString();
            emailparams[2] = GlobalVars.hostURL;
            emailparams[3] = "Parts Request";
            emailparams[4] = Session[GlobalVars.SESSION_USERNAME].ToString();
            String userEmail = toEmail;

            en.SendNotificationEmail(emailTemplate, GlobalVars.SENDER_EMAIL, senderName, userEmail, null, subject, emailparams);

        }

        protected void NotifyReAssignment(int prNumber, string email)
        {
            var pr = prDMO.Get(prNumber);

            if (pr == null)
                return;

            SendEmailNotification(
                subject: String.Format("Parts Request Re-Assignment notice for # {0} - {1}", pr.PRNumber, pr.Title),
                prNumber: prNumber,
                toEmail: email,
                emailTemplate: "PRReAssigned.txt");

            EventLogDMO.Add(new WinEventLog()
            {
                IssueID = prNumber,
                UserID = @User.Identity.Name,
                DocumentType = "PR",
                OperationType = "Email",
                Comments = "ReAssigned Approver: " + email
            });
        }

        protected void NotifyCompletion(int prNumber)
        {
            var pr = prDMO.Get(prNumber);

            if (pr == null)
                return;

            var u = userDMO.GetUserByID(pr.RequestorID);
            if ((u != null) && (!String.IsNullOrWhiteSpace(u.Email)))
            {
                SendEmailNotification(
                  subject: String.Format("Parts Request Completion notice for # {0} - {1}", pr.PRNumber, pr.Title),
                  prNumber: prNumber,
                  toEmail: u.Email,
                  emailTemplate: "PRCompleted.txt");

                EventLogDMO.Add(new WinEventLog()
                {
                    IssueID = prNumber,
                    UserID = @User.Identity.Name,
                    DocumentType = "PR",
                    OperationType = "Email",
                    Comments = "Completed: " + u.Email
                });
            }
        }

        public void NotifyRejection(int prNumber)
        {
            var pr = prDMO.Get(prNumber);

            if (pr == null)
                return;

            var u = userDMO.GetUserByID(pr.OriginatorID);
            if ((u != null) && (!String.IsNullOrWhiteSpace(u.Email)))
            {
                SendEmailNotification(
                  subject: String.Format("Parts Request Rejection notice for # {0} - {1}", pr.PRNumber, pr.Title),
                  prNumber: prNumber,
                  toEmail: u.Email,
                  emailTemplate: "PRReject.txt");

                EventLogDMO.Add(new WinEventLog()
                {
                    IssueID = prNumber,
                    UserID = @User.Identity.Name,
                    DocumentType = "PR",
                    OperationType = "Email",
                    Comments = "Rejected: " + u.Email
                });
            }
        }

        protected void NotifyApprovers(int prNumber, byte step)
        {
            try
            {
                string emailSentList = "";

                var pr = prDMO.Get(prNumber);

                if (pr == null)
                    throw new Exception("Invalid pr#");

                List<string> emailList = MiscDMO.GetApproverEmailListByDocument(
                    prNumber, step, (int)GlobalVars.DocumentType.PartsRequest).Distinct().ToList();

                foreach (string email in emailList)
                {
                    try
                    {
                        SendEmailNotification(
                            subject: String.Format("Parts Request Assignment notice for # {0} - {1}", pr.PRNumber, pr.Title),
                            prNumber: prNumber,
                            toEmail: email,
                            emailTemplate: "PRAssigned.txt");
                    }
                    catch (Exception ex)
                    {
                        HandleException(prNumber, ex, "email=" + email);
                    }

                    emailSentList += email + ",";
                }

                try
                {
                    EventLogDMO.Add(new WinEventLog() {
                        IssueID = prNumber, UserID = @User.Identity.Name, DocumentType = "PR", OperationType = "Email",
                        Comments = "Approvers for Step " + step.ToString() + ":" + emailSentList });
                }
                catch { }
            }
            catch (Exception e)
            {
                HandleException(prNumber, e, "Step=" + step.ToString());
            }
        }

        [HttpPost]
        public ActionResult Reject(int prNumber, byte currentStep, string comments)            
        {
            try
            {
                if (Session[GlobalVars.SESSION_USERID] != null)
                {
                    wfDMO.Reject(prNumber, currentStep, comments, (int)Session[GlobalVars.SESSION_USERID], (int)GlobalVars.DocumentType.PartsRequest);
                    NotifyRejection(prNumber);
                }
                else
                {
                    Response.Redirect("~/Account/Login");
                }

                return Content("OK");
            }
            catch (Exception e)
            {
                return HandleAPIException(prNumber, e);
            }
        }

        public ActionResult ApprovalLogHistory_Read([DataSourceRequest] DataSourceRequest request, int prNumber)
        {
            return Json(prDMO.GetApprovalLogHistory(prNumber).ToDataSourceResult(request), JsonRequestBehavior.AllowGet);
        }

        protected void NotifyAssignment(int prNumber, string email)
        {
            var pr = prDMO.Get(prNumber);

            if (pr == null)
                return;

            SendEmailNotification(
                subject: String.Format("Parts Request Assignment notice for # {0} - {1}", pr.PRNumber, pr.Title),
                prNumber: prNumber,
                toEmail: email,
                emailTemplate: "PRAssigned.txt");

            EventLogDMO.Add(new WinEventLog()
            {
                IssueID = prNumber,
                UserID = @User.Identity.Name,
                DocumentType = "PR",
                OperationType = "Email",
                Comments = "Assigned Approver: " + email
            });
        }

        [HttpPost]
        public void AddAdditionalApproval(int issueID, byte step, string userIDs)
        {
            var emailArray = "";
            try
            {
                emailArray = wfDMO.AddAdditionalApproval(issueID, userIDs, step, (int)GlobalVars.DocumentType.PartsRequest);
            }
            catch (Exception e)
            {
                HandleAPIException(issueID, e);
            }

            string emailSentList = "";
            string[] emaiList = emailArray.Split(new char[] { '~' });
            foreach (string email in emaiList)
            {
                if (email.Length > 0)
                {
                    NotifyAssignment(issueID, email);
                    emailSentList += email + ",";
                }

            }

            try
            {
                EventLogDMO.Add(new WinEventLog() { IssueID = issueID, UserID = @User.Identity.Name, DocumentType = "PR",
                    OperationType = "Email", Comments = "Additional Approver: " + emailSentList });
            }
            catch { }
        }

    }
}