using Knoks.Api.Controllers.Base;
using Knoks.Api.Entities;
using Knoks.Api.Filters;
using Knoks.Core.Entities;
using Knoks.Core.Entities.Args;
using Knoks.Core.Logic.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Net;
using HtmlAgilityPack;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System.Text.RegularExpressions;
using Knoks.Framework.Cryptography;
using Knoks.Framework.Security;

namespace Knoks.Api.Controllers
{
    [Route("v1/[controller]")]
    public class GeneralController : ApiConsumerController
    {
        #region Fields
        private readonly ILogger<GeneralController> _logger;
        private readonly IGeneralManager _generalManager;
        private readonly IUserManager _userManager;
        private readonly IExchangeManager _exchangeManager;
        private readonly IOperatorManager _operatorManager;
        #endregion

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="generalManager"></param>
        /// <param name="userManager"></param>
        /// <param name="operatorManager"></param>
        /// <param name="exchangeManager"></param>
        /// <param name="systemConfigurationManager"></param>
        public GeneralController(
            ILogger<GeneralController> logger,
            IGeneralManager generalManager,
            IUserManager userManager,
            IOperatorManager operatorManager,
            IExchangeManager exchangeManager,
            ISystemConfigurationManager systemConfigurationManager)
        {
            _logger = logger;
            _generalManager = generalManager;
            _userManager = userManager;
            _exchangeManager = exchangeManager;
            _operatorManager = operatorManager;
        }

        [HttpPut("User")]
        public async Task<User> UpdateUser([FromBody]UpdateUserArgs args)
        {
            if (!await _userManager.UserNameExists(args.UserName, this.ContextUserId))
            {
                if (!string.IsNullOrEmpty(args.UserName)) {
                    var name = args.UserName.Split(' ');
                    if (name.Count() > 1)
                    {
                        args.FirstName = name[0].TrimStart().TrimEnd();
                        args.LastName = args.UserName.Substring(name[0].Length).TrimStart().TrimEnd();
                    }
                    else {
                        args.FirstName = args.UserName;
                        args.LastName = string.Empty;
                    }
                }
                return await _userManager.UpdateUser(args);
            }
            throw new ApiResponseException(ApiErrorType.UserNameAlreadyExists, $"User with \"{args.UserName}\" username already exists.");
        }

        [HttpPost("NewUser")]
        [UserForbid]
        public async Task<User> RegisterUser([FromBody]CreateUserArgs args, [FromServices] IPasswordStrength _passwordStrength)
        {
            //args.Source = "web";
            //args.CreateOrigin = "0";
            args.UserTypeId = 1; // Demo

            if (await _passwordStrength.CheckStrength(args.Password) < PasswordScore.Medium)
            {
                throw new ApiResponseException(ApiErrorType.PasswordIsNotStrong, "Password is not enough strong.");
            }

            if (await _userManager.UserExists(args.UserName))
            {
                throw new ApiResponseException(ApiErrorType.UserNameAlreadyExists, $"User with \"{args.UserName}\" username already exists.");
            }

            if (await _userManager.UserEmailExists(args.Email))
            {
                throw new ApiResponseException(ApiErrorType.UserEmailAlreadyExists, $"User with \"{args.Email}\" email already exists.");
            }

            return await _userManager.CreateUser(args);
        }

        [HttpPost("ParseUrl")]
        public async Task<SiteMeta> ParseUrl([FromBody]ReferenceUrlArgs args)
        {
            return await Task.Run(() =>
            {

                var meta = new SiteMeta();
                string xmlStr;
                using (var wc = new WebClient())
                {
                    Regex RgxUrl = new Regex("(([a-zA-Z][0-9a-zA-Z+\\-\\.]*:)?/{0,2}[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?(#[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?");
                    if (args.Url != null && RgxUrl.IsMatch(args.Url))
                    {
                        try
                        {
                            wc.Headers.Add("User-Agent: Other");
                            xmlStr = wc.DownloadString(args.Url);
                        }
                        catch(Exception ex)
                        {
                            _logger.LogError(ex, ex.Message);
                            return null;
                        }
                    }
                    else return null;
                }

                var doc = new HtmlDocument();
                try
                {
                    doc.LoadHtml(xmlStr);
                }
                catch (Exception)
                {
                    return null;
                }
                if (doc != null)
                {
                    var titleelement = doc.DocumentNode.SelectNodes("//meta[@property='og:title']");
                    if (titleelement != null)
                    {
                        if (titleelement.Count > 0)
                        {
                            var title = titleelement.First().Attributes["content"].Value;
                            meta.Header = title;
                        }
                    }

                    var descelement = doc.DocumentNode.SelectNodes("//meta[@property='og:description']");
                    if (descelement != null)
                    {
                        if (titleelement.Count > 0)
                        {
                            var desc = descelement.First().Attributes["content"].Value;
                            meta.Description = desc;
                        }
                    }

                    var urllelement = doc.DocumentNode.SelectNodes("//meta[@property='og:url']");
                    if (urllelement != null)
                    {
                        if (urllelement.Count > 0)
                        {
                            var urll = urllelement.First().Attributes["content"].Value;
                            meta.Url = urll;
                        }
                    }

                    var imageelement = doc.DocumentNode.SelectNodes("//meta[@property='og:image']");
                    if (imageelement != null)
                    {
                        if (imageelement.Count > 0)
                        {
                            var image = imageelement.First().Attributes["content"].Value;
                            meta.Image = image;
                        }
                    }
                }



                try
                {


                    var jsonString = doc.DocumentNode.SelectNodes("//script[@type='application/ld+json']").First().InnerHtml;
                    JObject json = JObject.Parse(jsonString);
                    var t = json.SelectToken("datePublished").ToString();
                    meta.Published = Convert.ToDateTime(t);
                    var name = json.SelectToken("publisher").SelectToken("name").ToString();

                    meta.PublishedBy = name;

                }
                catch
                {
                    return meta;
                }
                return meta;
            });
        }

        [HttpGet("Countries")]
        public Task<ApiArray<Country>> GetCountries()
        {
            return GetCountries(null);
        }

        [HttpGet("UserRankCount")]
        public async Task<ApiArray<RankCount>> GetUserRanksCount()
        {
            return ApiArray(await _generalManager.GetUserRankCount(this.ContextUserId));
        }

        [HttpGet("KnoksLossCount")]
        public async Task<ApiArray<KnokLossCount>> KnoksLossCount()
        {
            return ApiArray(await _generalManager.GetKnoksLossCount(this.ContextUserId));
        }
        
        [HttpGet("Countries/{CountryCode2}")]
        public async Task<ApiArray<Country>> GetCountries([FromRoute]string countryCode2)
        {
            return ApiArray(await _generalManager.GetCountries(countryCode2));
        }

        [HttpGet("Languages")]
        public async Task<ApiArray<Language>> GetLanguages()
        {
            return ApiArray(await _generalManager.GetLanguages());
        }

        [HttpGet("KnokStatuses")]
        public async Task<ApiArray<KnokStatus>> GetKnokStatuses()
        {
            return ApiArray(await _generalManager.GetKnokStatuses());
        }

        [HttpGet("KnokStartvalues")]
        public async Task<ApiObject<ExchangeStart>> GetKnokStartValues([FromQuery]int exchangeId, [FromQuery]string currency1, [FromQuery]string currency2)
        {
            return ApiObject(await _exchangeManager.GetTradingPairRate(exchangeId, currency1, currency2));
        }

        [HttpPost("AddOngoingKnokFeedback")]
        public async Task<ApiObject<bool>> AddOngoingKnokFeedback([FromBody]OngoingKnokFeedbackArgs feedback)
        {
           await _generalManager.AddKnokFeedBack(new Feedback()
           {
               KnokId = feedback.KnokId,
               UserId = feedback.UserId,
               FeedbackType = FeedbackType.Ongoing,
               Comment = feedback.Comment,
               Clear = feedback.Clear,
               Educational = feedback.Educational,
               Comprehensive = feedback.Comprehensive
           });
           return await Task.FromResult(ApiObject<bool>(true));
        }

        [HttpPost("AddEndedKnokFeedback")]
        public async Task<ApiObject<bool>> AddEndedKnokFeedback([FromBody]EndedKnokFeedbackArgs feedback)
        {
            await _generalManager.AddKnokFeedBack(new Feedback() {
                KnokId = feedback.KnokId,
                UserId = feedback.UserId,
                FeedbackType = FeedbackType.RecentlyEnded,
                Comment = feedback.Comment,
                KnokBenefit = feedback.KnokBenefit
            });
            return await Task.FromResult(ApiObject<bool>(true));
        }

        [UserForbid, OperatorForbid]
        [HttpPost("Operator")]
        public async Task<Operator> CreateOperator([FromBody]CreateOperatorArgs args)
        {
            return await _operatorManager.CreateOperator(args);
        }

        [HttpGet("{user}/avatar")]
        [AllowAnonymous]
        public FileStreamResult GetAvatar([FromRoute]string user, [FromServices] IUidManager uidManager)
        {
            int? userId = uidManager.ToIntValue(user, typeof(User), Constants.avatarCause);
            if (userId == null)
            {
                throw new ArgumentNullException("User was no provided");
            }
            var result = _userManager.GetAvatarFile(userId.Value);
            if (result.File != null)
            {
                result.ContentType = "image/png";
                return File(result.File, result.ContentType, result.LastModified, null);// "image/png");
            }
            else
            {
                return null;
            }
        }

        [HttpGet("Update")]
        public async Task<ApiObject<bool>> Update([FromServices] IExchangeManager exchangeManager)
        {
            await exchangeManager.UpdateExchanges();
            return new ApiObject<bool>(true);
        }

        [HttpGet("FilterCurrencies")]
        public async Task<ApiArray<Currency>> GetFilterCurrencies([FromQuery] long? userId, [FromQuery] bool? availableKnoks, [FromQuery] bool? activeKnoks, [FromQuery] bool? endedKnoks)
        {
            return new ApiArray<Currency>(await _generalManager.GetFilterCurrencies(userId, availableKnoks, activeKnoks, endedKnoks));
        }

        [HttpGet("Currencies/{currency?}")]
        public async Task<ApiArray<Currency>> GetCurrencies([FromRoute] string currency)
        {
            return new ApiArray<Currency>(await _generalManager.GetCurrencies(currency));
        }

        [HttpGet("Exchanges")]
        public async Task<ApiArray<Exchange>> GetExchanges([FromServices] IExchangeManager exchangeManager, [FromQuery] long? userId, [FromQuery] bool? availableKnoks, [FromQuery] bool? activeKnoks, [FromQuery] bool? endedKnoks)
        {
            return new ApiArray<Exchange>(await _generalManager.GetExchanges(userId, availableKnoks, activeKnoks, endedKnoks));
        }

        [HttpGet("Configs")]
        public Task<ApiDictionary<string>> GetConfigs([FromQuery]IList<string> keys, [FromServices]IKnokManager knokManager)
        {
            var settings = knokManager.Settings.ToDictionary(it => /*"KnokSettings." +*/ it.Key, it => it.Value);
            settings.Add("tagOtherMagicValue", "-1");
            return Task.FromResult(
                new ApiDictionary<string>(
                    settings
                //new Dictionary<string, string>() { { "KnokSettings.MaximumDuration", knokManager.MaximumDuration.ToString() }//days       }
                ));
        }

        [HttpGet("Tags/{isTechnical:int}")]
        public async Task<ApiArray<AnalysisTag>> GetTags([FromRoute] int isTechnical, [FromServices]Core.Data.Interfaces.ITagDao tagDao)
        {
            return new ApiArray<AnalysisTag>((await tagDao.GetTags(isTechnical == 1 ? isTechnical : 2)));
        }

        [HttpPost("SendPasswordReset")]
        [AllowAnonymous]
        public async Task<ApiObject<bool>> SendPasswordReset([FromBody] RetrievePasswordArgs args)
        {
            if (await _userManager.UserEmailExists(args.Email))
            {
                await _userManager.SendPasswordResetLink(args.Email, args.Url);
                return new ApiObject<bool>(true);
            }
            throw new ApiResponseException(ApiErrorType.UserEmailDoesNotExist, $"User with \"{args.Email}\" email does not exist.");
        }

        [HttpPost("CheckPasswordResetToken")]
        [AllowAnonymous]
        public async Task<ApiObject<bool>> CheckPasswordResetToken([FromBody] CheckTokenArgs args)
        {
            if (args == null || string.IsNullOrEmpty(args.Token))
            {
                throw new ApiResponseException(ApiErrorType.UserEmailDoesNotExist, $"Token is incorrect.");
            }
            var result = await _userManager.CheckPasswordResetToken(args.Token);
            if (!result)
            {
                throw new ApiResponseException(ApiErrorType.UserEmailDoesNotExist, $"Token is incorrect.");
            }
            return new ApiObject<bool>(true);
        }

        [HttpPost("PasswordReset")]
        [AllowAnonymous]
        public async Task<ApiObject<bool>> PasswordReset([FromBody] ResetPasswordArgs args, [FromServices] IPasswordStrength _passwordStrength)
        {
            if (await _passwordStrength.CheckStrength(args.NewPassword) < PasswordScore.Medium)
            {
                throw new ApiResponseException(ApiErrorType.PasswordIsNotStrong, "Password is not enough strong.");
            }
            return new ApiObject<bool>(await _userManager.PasswordReset(args.Token, args.NewPassword));
        }
    }
}