using System;
using System.Collections.Generic;
using System.Linq;
using BirdsiteLive.Twitter;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using BirdsiteLive.ActivityPub;
using BirdsiteLive.ActivityPub.Models;
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.ActivityPub.Converters;
using BirdsiteLive.Common.Settings;
using BirdsiteLive.DAL.Models;
using BirdsiteLive.Domain.Enum;
using System.Net.Http;
using BirdsiteLive.Common.Regexes;
using Microsoft.Extensions.Logging;

namespace BirdsiteLive.Domain
{
    public class MigrationService
    {
        private readonly InstanceSettings _instanceSettings;
        private readonly ITheFedInfoService _theFedInfoService;
        private readonly ITwitterTweetsService _twitterTweetsService;
        private readonly IActivityPubService _activityPubService;
        private readonly ITwitterUserDal _twitterUserDal;
        private readonly IFollowersDal _followersDal;
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly ILogger<MigrationService> _logger;

        #region Ctor
        public MigrationService(ITwitterTweetsService twitterTweetsService, IActivityPubService activityPubService, ITwitterUserDal twitterUserDal, IFollowersDal followersDal, InstanceSettings instanceSettings, ITheFedInfoService theFedInfoService, IHttpClientFactory httpClientFactory, ILogger<MigrationService> logger)
        {
            _twitterTweetsService = twitterTweetsService;
            _activityPubService = activityPubService;
            _twitterUserDal = twitterUserDal;
            _followersDal = followersDal;
            _instanceSettings = instanceSettings;
            _theFedInfoService = theFedInfoService;
            _httpClientFactory = httpClientFactory;
            _logger = logger;
        }
        #endregion

        public string GetMigrationCode(string acct)
        {
            var hash = GetHashString(acct);
            return $"[[BirdsiteLIVE-MigrationCode|{hash.Substring(0, 10)}]]";
        }

        public string GetDeletionCode(string acct)
        {
            var hash = GetHashString(acct);
            return $"[[BirdsiteLIVE-DeletionCode|{hash.Substring(0, 10)}]]";
        }

        public bool ValidateTweet(string acct, string tweetId, MigrationTypeEnum type)
        {
            string code;
            if (type == MigrationTypeEnum.Migration)
                code = GetMigrationCode(acct);
            else if (type == MigrationTypeEnum.Deletion)
                code = GetDeletionCode(acct);
            else
                throw new NotImplementedException();

            var castedTweetId = ExtractedTweetId(tweetId);
            var tweet = _twitterTweetsService.GetTweet(castedTweetId);

            if (tweet == null)
                throw new Exception("Tweet not found");

            if (tweet.CreatorName.Trim().ToLowerInvariant() != acct.Trim().ToLowerInvariant())
                throw new Exception($"Tweet not published by @{acct}");

            if (!tweet.MessageContent.Contains(code))
            {
                var message = "Tweet don't have migration code";
                if (type == MigrationTypeEnum.Deletion)
                    message = "Tweet don't have deletion code";

                throw new Exception(message);
            }

            return true;
        }

        private long ExtractedTweetId(string tweetId)
        {
            if (string.IsNullOrWhiteSpace(tweetId))
                throw new ArgumentException("No provided Tweet ID");

            long castedId;
            if (long.TryParse(tweetId, out castedId))
                return castedId;

            var urlPart = tweetId.Split('/').LastOrDefault();
            if (long.TryParse(urlPart, out castedId))
                return castedId;

            throw new ArgumentException("Unvalid Tweet ID");
        }

        public async Task<ValidatedFediverseUser> ValidateFediverseAcctAsync(string fediverseAcct)
        {
            if (string.IsNullOrWhiteSpace(fediverseAcct))
                throw new ArgumentException("Please provide Fediverse account");

            if (!fediverseAcct.Contains('@') || !fediverseAcct.StartsWith("@") || fediverseAcct.Trim('@').Split('@').Length != 2)
                throw new ArgumentException("Please provide valid Fediverse handle");

            var objectId = await _activityPubService.GetUserIdAsync(fediverseAcct);
            var user = await _activityPubService.GetUser(objectId);

            var result = new ValidatedFediverseUser
            {
                FediverseAcct = fediverseAcct,
                ObjectId = objectId,
                User = user,
                IsValid = user != null
            };

            return result;
        }

        public async Task MigrateAccountAsync(ValidatedFediverseUser validatedUser, string acct)
        {
            // Apply moved to
            var twitterAccount = await _twitterUserDal.GetTwitterUserAsync(acct);
            if (twitterAccount == null)
            {
                await _twitterUserDal.CreateTwitterUserAsync(acct, -1, validatedUser.ObjectId, validatedUser.FediverseAcct);
                twitterAccount = await _twitterUserDal.GetTwitterUserAsync(acct);
            }

            twitterAccount.MovedTo = validatedUser.User.id;
            twitterAccount.MovedToAcct = validatedUser.FediverseAcct;
            twitterAccount.LastSync = DateTime.UtcNow;
            await _twitterUserDal.UpdateTwitterUserAsync(twitterAccount);

            // Notify Followers
            var message = $@"<p>[BSL MIRROR SERVICE NOTIFICATION]<br/>This bot has been disabled by its original owner.<br/>It has been redirected to {validatedUser.FediverseAcct}.</p>";
            NotifyFollowers(acct, twitterAccount, message);
        }

        private void NotifyFollowers(string acct, SyncTwitterUser twitterAccount, string message)
        {
            var t = Task.Run(async () =>
            {
                var followers = await _followersDal.GetFollowersAsync(twitterAccount.Id);
                foreach (var follower in followers)
                {
                    try
                    {
                        var noteId = Guid.NewGuid().ToString();
                        var actorUrl = UrlFactory.GetActorUrl(_instanceSettings.Domain, acct);
                        var noteUrl = UrlFactory.GetNoteUrl(_instanceSettings.Domain, acct, noteId);

                        //var to = validatedUser.ObjectId;
                        var to = follower.ActorId;
                        var cc = new string[0];

                        var note = new Note
                        {
                            id = noteUrl,

                            published = DateTime.UtcNow.ToString("s") + "Z",
                            url = noteUrl,
                            attributedTo = actorUrl,

                            to = new[] { to },
                            cc = cc,

                            content = message,
                            tag = new Tag[]{
                                new Tag()
                                {
                                    type = "Mention",
                                    href = follower.ActorId,
                                    name = $"@{follower.Acct}@{follower.Host}"
                                }
                            },
                        };

                        if (!string.IsNullOrWhiteSpace(follower.SharedInboxRoute))
                            await _activityPubService.PostNewNoteActivity(note, acct, noteId, follower.Host, follower.SharedInboxRoute);
                        else
                            await _activityPubService.PostNewNoteActivity(note, acct, noteId, follower.Host, follower.InboxRoute);
                    }
                    catch (Exception e)
                    {
                        _logger.LogError(e, e.Message);
                    }
                }
            });
        }

        public async Task DeleteAccountAsync(string acct)
        {
            // Apply deleted state
            var twitterAccount = await _twitterUserDal.GetTwitterUserAsync(acct);
            if (twitterAccount == null)
            {
                await _twitterUserDal.CreateTwitterUserAsync(acct, -1);
                twitterAccount = await _twitterUserDal.GetTwitterUserAsync(acct);
            }

            twitterAccount.Deleted = true;
            twitterAccount.LastSync = DateTime.UtcNow;
            await _twitterUserDal.UpdateTwitterUserAsync(twitterAccount);

            // Notify Followers
            var message = $@"<p>[BSL MIRROR SERVICE NOTIFICATION]<br/>This bot has been deleted by its original owner.<br/></p>";
            NotifyFollowers(acct, twitterAccount, message);

            // Delete remote accounts
            DeleteRemoteAccounts(acct);
        }

        private void DeleteRemoteAccounts(string acct)
        {
            var t = Task.Run(async () =>
            {
                var allUsers = await _followersDal.GetAllFollowersAsync();

                var followersWtSharedInbox = allUsers
                    .Where(x => !string.IsNullOrWhiteSpace(x.SharedInboxRoute))
                    .GroupBy(x => x.Host)
                    .ToList();
                foreach (var followerGroup in followersWtSharedInbox)
                {
                    var host = followerGroup.First().Host;
                    var sharedInbox = followerGroup.First().SharedInboxRoute;

                    var t1 = Task.Run(async () =>
                    {
                        try
                        {
                            await _activityPubService.DeleteUserAsync(acct, host, sharedInbox);
                        }
                        catch (Exception e)
                        {
                            _logger.LogError(e, e.Message);
                        }
                    });
                }

                var followerWtInbox = allUsers
                    .Where(x => !string.IsNullOrWhiteSpace(x.SharedInboxRoute))
                    .ToList();
                foreach (var followerGroup in followerWtInbox)
                {
                    var host = followerGroup.Host;
                    var sharedInbox = followerGroup.InboxRoute;

                    var t1 = Task.Run(async () =>
                    {
                        try
                        {
                            await _activityPubService.DeleteUserAsync(acct, host, sharedInbox);
                        }
                        catch (Exception e)
                        {
                            _logger.LogError(e, e.Message);
                        }
                    });
                }
            });
        }

        public async Task TriggerRemoteMigrationAsync(string id, string tweetIdStg, string handle)
        {
            var url = $"https://{{0}}/migration/move/{{1}}/{{2}}/{handle}";
            await ProcessRemoteMigrationAsync(id, tweetIdStg, url);

        }

        public async Task TriggerRemoteDeleteAsync(string id, string tweetIdStg)
        {
            var url = $"https://{{0}}/migration/delete/{{1}}/{{2}}";
            await ProcessRemoteMigrationAsync(id, tweetIdStg, url);
        }

        private async Task ProcessRemoteMigrationAsync(string id, string tweetIdStg, string urlPattern)
        {
            try
            {
                var instances = await RetrieveCompatibleBslInstancesAsync();
                var tweetId = ExtractedTweetId(tweetIdStg);

                foreach (var instance in instances)
                {
                    try
                    {
                        var host = instance.Host;
                        if(!UrlRegexes.Domain.IsMatch(host)) continue;

                        var url = string.Format(urlPattern, host, id, tweetId);

                        var client = _httpClientFactory.CreateClient();
                        var result = await client.PostAsync(url, null);
                        result.EnsureSuccessStatusCode();
                    }
                    catch (Exception e)
                    {
                        _logger.LogError(e, e.Message);
                    }
                }
            }
            catch (Exception e)
            {
                _logger.LogError(e, e.Message);
            }
        }

        private async Task<List<BslInstanceInfo>> RetrieveCompatibleBslInstancesAsync()
        {
            var instances = await _theFedInfoService.GetBslInstanceListAsync();
            var filteredInstances = instances
                .Where(x => x.Version >= new Version(0, 21, 0))
                .Where(x => string.Compare(x.Host, 
                    _instanceSettings.Domain, 
                    StringComparison.InvariantCultureIgnoreCase) != 0)
                .ToList();
            return filteredInstances;
        }

        private byte[] GetHash(string inputString)
        {
            using (HashAlgorithm algorithm = SHA256.Create())
                return algorithm.ComputeHash(Encoding.UTF8.GetBytes(inputString));
        }

        private string GetHashString(string inputString)
        {
            StringBuilder sb = new StringBuilder();
            foreach (byte b in GetHash(inputString))
                sb.Append(b.ToString("X2"));

            return sb.ToString();
        }
    }

    public class ValidatedFediverseUser
    {
        public string FediverseAcct { get; set; }
        public string ObjectId { get; set; }
        public Actor User { get; set; }
        public bool IsValid { get; set; }
    }
}