using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Identity;
using Microsoft.JSInterop;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using HyperCube.Models;
using Console = HyperCube.Utils.AdvConsole;

namespace HyperCube.Pages
{
    public partial class DocEdit : ComponentBase
    {
        [Parameter]
        public int DocID { get; set; }

        [Inject]
        public NavigationManager NavigationManager { get; set; }
        [Inject]
        public IJSRuntime JsRuntime { get; set; }
        [Inject]
        public AuthenticationStateProvider AuthenticationStateProvider { get; set; }
        [Inject]
        public UserManager<IdentityUser> UserManager { get; set; }

        const string FOLDER_NAME = "articles_storage";
        const long MAX_FILE_SIZE = 5120000; //bytes

        string transactionId;
        ArticleModel articleClone = new();
        ArticleModel article = new();
        AccountModel currentAcc = new();
        AccountModel initiatorAcc = new();
        string status;
        string header;
        string storageFolderPath;
        MemoryStream memoryStream;
        ModalInfo modalInfo_error { get; set; }
        ModalInfo modalInfo_transac { get; set; }
        ModalLoading modalLoading { get; set; }
        string fullName { get { return FOLDER_NAME + "/" + article.FilenameReal; } }
        int editsCount;

        bool IsSubmitDisabled = false;

        protected override async Task OnInitializedAsync()
        {
            currentAcc = await GetCurrentAcc();

            string path = AppDomain.CurrentDomain.BaseDirectory+@"wwwroot";
            storageFolderPath = (Path.Combine(path, FOLDER_NAME));
            Console.WriteLine("docedit OnInitializedAsync storageFolderPath2 " + storageFolderPath);
            if (DocID > 0)
            {
                header = "Проверка материала";

                MySQLConnector dbCon = MySQLConnector.Instance();
                
                articleClone = await dbCon.SQLSelectArticle(DocID);
                article = (ArticleModel)articleClone.Clone();

                string initiator = await article.GetInitiatorUUID(ArticleStatus.Saved);
                initiatorAcc = AccountModel.Find(initiator);
                status = $"Article ID={DocID} loaded, status: {article.Status}, initiator: {initiatorAcc.Name}";
            }
            else
                header = "Загрузка материала";


            //await InitializeAccount();

            //int count = await articleModel.GetEditsCount();
            //int countbyid = await articleModel.GetEditsCount(currentAcc.UUID);
            //header += $", uuid:{currentAcc.UUID}, name: {currentAcc.Name}, edits count:{count}, count by accid: {countbyid}";
        }

        public async Task<string> NewProjectSmopp(long articleId)
        {
            WebRequest wrGETURL;
            wrGETURL = WebRequest.Create("http://dev.prmsys.net/makeproject.php?pt=232&aid="+ articleId);
            var response = await wrGETURL.GetResponseAsync();
            Stream dataStream = response.GetResponseStream();

            StreamReader reader = new(dataStream);            
            return reader.ReadToEnd();
        }

        private async Task HandleSubmit()
        {
            Console.WriteLine($"HandleSubmit, docID: {DocID}.");

            modalLoading.Open();

            /// form validation
            List<string> errorFields = ValidateForm<ArticleModel>(article);
            if (errorFields.Count > 0)
            {
                modalLoading.Close();
                string invalid_fields = string.Join(", ", errorFields);
                modalInfo_error.Open($"Не заполнены поля: {invalid_fields}");
                Console.WriteLine($"HandleSubmit. Required fields: '{invalid_fields}' is not filled.");
                return;
            }          

            /// all is fine, continue
            MySQLConnector dbCon = MySQLConnector.Instance();
            long id;
            string stringSQL;            

            if (DocID > 0)
            {
                id = DocID;
                stringSQL = $"UPDATE articles " +
                    $"SET filename='{article.Filename}', article_name='{article.Name}', authors='{article.Authors}', " +
                        $"date_publish='{article.PublishDate:yyyy-MM-dd}', annotation='{article.Annotation}', " +
                        $"keywords='{article.Keywords}', rating={article.Rating}, file_hash='{article.HashSum}' " +
                    $"WHERE id={DocID}";
                await dbCon.SQLInsert(stringSQL);
            }
            else
            {
                stringSQL = $"INSERT INTO articles (filename, article_name, authors, date_publish, annotation, keywords, file_hash) " +
                    $"VALUES ('{article.Filename}', '{article.Name}', '{article.Authors}', '{article.PublishDate:yyyy-MM-dd}'," +
                        $"'{article.Annotation}', '{article.Keywords}', '{article.HashSum}')";
                id = await dbCon.SQLInsert(stringSQL);
                NewProjectSmopp(id);
            }          

            /// tmp
            int action_type = DocID > 0 ? 2 : 1;

            stringSQL = $"INSERT INTO actions_history (article_id, action_type, acc_id) " +
                $"VALUES ('{id}', '{action_type}', '{currentAcc.UUID}')";
            await dbCon.SQLInsert(stringSQL);

            Dictionary<string, PropertyInfo> propDict = Compare.SimpleCompare<ArticleModel>(article, articleClone);
            foreach (KeyValuePair<string, PropertyInfo> prop in propDict)
            {
                //Console.WriteLine($"property name: {prop.Key}, value: {prop.Value.GetValue(articleModel, null)}");

                stringSQL = $"INSERT INTO articles_edit_log (article_id, acc_id, field_name, field_prevvalue, field_newvalue) " +
                    $"VALUES ('{id}', '{currentAcc.UUID}', '{prop.Key}', '{prop.Value.GetValue(articleClone, null)}', '{prop.Value.GetValue(article, null)}')";
                await dbCon.SQLInsert(stringSQL);
            }

            //dbCon.Close();

            if (DocID > 0)
            {
                status = propDict.Count > 0 ? "All changes saved, article has veryfied." : "Article verifyed without any changes.";
                //transactionId = await Verify();
                Console.WriteLine("transactionId found " + transactionId);

                ///tmp
                editsCount = await article.GetEditsCount(currentAcc.UUID);
                modalInfo_transac.Open("");
            }
            else
            {
                string fullpath = Path.Combine(storageFolderPath, $"{id}_{article.Filename}");
                Directory.CreateDirectory(storageFolderPath);
                FileStream fs = new(fullpath, FileMode.Create, FileAccess.Write);
                memoryStream.Position = 0;
                await memoryStream.CopyToAsync(fs);

                Console.WriteLine($"User has saved new article data, {id}_{article.Filename}, memory size:{memoryStream.Length}b, file size: {fs.Length}b");
                memoryStream.Close();
                fs.Close();

                status = "New article data saved.";

                bool confirmed = await JsRuntime.InvokeAsync<bool>("confirm", "Хотите загрузить еще статью?");
                if (confirmed)
                    NavigationManager.NavigateTo("docedit", true);
                else
                    NavigationManager.NavigateTo("");
            }

            /// reloading articles
            await AppData.LoadArticles();
        }

        private async Task HandleSelection(InputFileChangeEventArgs e)
        {
            modalLoading.Open();

            IBrowserFile file = e.File;
            if (file != null)
            {
                Stream stream = file.OpenReadStream(MAX_FILE_SIZE);
                memoryStream = new();
                await stream.CopyToAsync(memoryStream);
                status = $"Finished loading {memoryStream.Length} bytes from {file.Name}";
                Console.WriteLine(status);

                /// calculating hash
                string hash = await CalculateHashSum(memoryStream);
                Console.WriteLine($"Hash: {hash}");

                /// checking hash
                MySQLConnector dbCon = MySQLConnector.Instance();
                string stringSQL;
                stringSQL = $"SELECT COUNT(*) FROM articles WHERE file_hash='{hash}'";
                int count = await dbCon.SQLSelectCount(stringSQL);
                //Console.WriteLine($"File duplicate check, founded files: {count}");

                if (count < 1)
                {
                    //articleClone = DocParse.GetBaseProperties(memoryStream);
                    articleClone = DocParse.GetBaseProperties("");
                    articleClone.Filename = file.Name;
                    articleClone.HashSum = hash;
                    article = (ArticleModel)articleClone.Clone();

                    IsSubmitDisabled = false;
                }
                else
                {
                    status = $"File duplicate founded, hash: {hash}.";
                    Console.WriteLine(status);

                    IsSubmitDisabled = true;
                    memoryStream.Close();
                    stream.Close();
                    modalInfo_error.Open("Загрузка не удалась, такой документ уже есть в системе.");
                }
            }

            modalLoading.Close();
        }

        private async Task Cancel()
        {
            bool confirmed = await JsRuntime.InvokeAsync<bool>("confirm", "Вы уверены, что хотите отклонить статью?");
            if (confirmed)
            {
                ///какая-то логика отмены...
                NavigationManager.NavigateTo("");
            }
        }

        //public async Task InitializeAccount()
        //{
        //    AccountModel.Current = await GetCurrentAcc();
        //    Console.WriteLine("InitializeAccount in DocEdit " + AccountModel.Current.Name);
        //}

        private async Task<AccountModel> GetCurrentAcc()
        {
            AccountModel account = new();

            var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
            var user = authState.User;

            if (user.Identity.IsAuthenticated)
            {
                var currentUser = await UserManager.GetUserAsync(user);
                account.UUID = currentUser.Id;
                //account.Name = currentUser.UserName;
                //account.Email = currentUser.Email;

                var acc = AccountModel.Find(account.UUID);
                if (acc != null)
                    account = acc;
                ///tmp

                //account.AccRole = Role.User;
                return account;
            }

            return null;
        }

        private async Task<string> CalculateHashSum(MemoryStream ms)
        {
            MD5CryptoServiceProvider md5Provider = new();
            ms.Position = 0;
            byte[] hash = await md5Provider.ComputeHashAsync(ms);
            return Convert.ToBase64String(hash);
        }

        private List<string> ValidateForm<T>(T obj)
        {
            var props = typeof(T).GetProperties().Where(pi => Attribute.IsDefined(pi, typeof(RequiredAttribute)));
            List<string> result = new();
            foreach (var prop in props)
            {
                var val = prop.GetValue(obj, null);
                if (val == null || val?.ToString().Length == 0)                    
                    result.Add(prop.Name);

                //Console.WriteLine($"Required field '{prop.Name}' is not filled.");
            }            
            return result;
        }
    }
}
