using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

using PluralKit.Core;

namespace PluralKit.Bot
{
    public class Admin
    {
        private readonly BotConfig _botConfig;
        private readonly IDatabase _db;
        private readonly ModelRepository _repo;

        public Admin(BotConfig botConfig, IDatabase db, ModelRepository repo)
        {
            _botConfig = botConfig;
            _db = db;
            _repo = repo;
        }

        public async Task UpdateSystemId(Context ctx)
        {
            AssertBotAdmin(ctx);
            
            var target = await ctx.MatchSystem();
            if (target == null)
                throw new PKError("Unknown system.");

            var newHid = ctx.PopArgument();
            if (!Regex.IsMatch(newHid, "^[a-z]{5}$"))
                throw new PKError($"Invalid new system ID `{newHid}`.");

            var existingSystem = await _db.Execute(c => _repo.GetSystemByHid(c, newHid));
            if (existingSystem != null)
                throw new PKError($"Another system already exists with ID `{newHid}`.");

            if (!await ctx.PromptYesNo($"Change system ID of `{target.Hid}` to `{newHid}`?", "Change"))
                throw new PKError("ID change cancelled.");

            await _db.Execute(c => _repo.UpdateSystem(c, target.Id, new SystemPatch {Hid = newHid}));
            await ctx.Reply($"{Emojis.Success} System ID updated (`{target.Hid}` -> `{newHid}`).");
        }
        
        public async Task UpdateMemberId(Context ctx)
        {
            AssertBotAdmin(ctx);
            
            var target = await ctx.MatchMember();
            if (target == null)
                throw new PKError("Unknown member.");

            var newHid = ctx.PopArgument();
            if (!Regex.IsMatch(newHid, "^[a-z]{5}$"))
                throw new PKError($"Invalid new member ID `{newHid}`.");

            var existingMember = await _db.Execute(c => _repo.GetMemberByHid(c, newHid));
            if (existingMember != null)
                throw new PKError($"Another member already exists with ID `{newHid}`.");

            if (!await ctx.PromptYesNo($"Change member ID of **{target.NameFor(LookupContext.ByNonOwner)}** (`{target.Hid}`) to `{newHid}`?", "Change"))
                throw new PKError("ID change cancelled.");
            
            await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch {Hid = newHid}));
            await ctx.Reply($"{Emojis.Success} Member ID updated (`{target.Hid}` -> `{newHid}`).");
        }

        public async Task UpdateGroupId(Context ctx)
        {
            AssertBotAdmin(ctx);

            var target = await ctx.MatchGroup();
            if (target == null)
                throw new PKError("Unknown group.");

            var newHid = ctx.PopArgument();
            if (!Regex.IsMatch(newHid, "^[a-z]{5}$"))
                throw new PKError($"Invalid new group ID `{newHid}`.");

            var existingGroup = await _db.Execute(c => _repo.GetGroupByHid(c, newHid));
            if (existingGroup != null)
                throw new PKError($"Another group already exists with ID `{newHid}`.");

            if (!await ctx.PromptYesNo($"Change group ID of **{target.Name}** (`{target.Hid}`) to `{newHid}`?", "Change"))
                throw new PKError("ID change cancelled.");

            await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch {Hid = newHid}));
            await ctx.Reply($"{Emojis.Success} Group ID updated (`{target.Hid}` -> `{newHid}`).");
        }

        public async Task SystemMemberLimit(Context ctx)
        {
            AssertBotAdmin(ctx);

            var target = await ctx.MatchSystem();
            if (target == null)
                throw new PKError("Unknown system.");

            var currentLimit = target.MemberLimitOverride ?? Limits.MaxMemberCount;
            if (!ctx.HasNext())
            {
                await ctx.Reply($"Current member limit is **{currentLimit}** members.");
                return;
            }

            var newLimitStr = ctx.PopArgument();
            if (!int.TryParse(newLimitStr, out var newLimit))
                throw new PKError($"Couldn't parse `{newLimitStr}` as number.");

            if (!await ctx.PromptYesNo($"Update member limit from **{currentLimit}** to **{newLimit}**?", "Update"))
                throw new PKError("Member limit change cancelled.");
            
            await using var conn = await _db.Obtain();
            await _repo.UpdateSystem(conn, target.Id, new SystemPatch
            {
                MemberLimitOverride = newLimit
            });
            await ctx.Reply($"{Emojis.Success} Member limit updated.");
        }

        public async Task SystemGroupLimit(Context ctx)
        {
            AssertBotAdmin(ctx);

            var target = await ctx.MatchSystem();
            if (target == null)
                throw new PKError("Unknown system.");

            var currentLimit = target.GroupLimitOverride ?? Limits.MaxGroupCount;
            if (!ctx.HasNext())
            {
                await ctx.Reply($"Current group limit is **{currentLimit}** groups.");
                return;
            }

            var newLimitStr = ctx.PopArgument();
            if (!int.TryParse(newLimitStr, out var newLimit))
                throw new PKError($"Couldn't parse `{newLimitStr}` as number.");

            if (!await ctx.PromptYesNo($"Update group limit from **{currentLimit}** to **{newLimit}**?", "Update"))
                throw new PKError("Group limit change cancelled.");

            await using var conn = await _db.Obtain();
            await _repo.UpdateSystem(conn, target.Id, new SystemPatch
            {
                GroupLimitOverride = newLimit
            });
            await ctx.Reply($"{Emojis.Success} Group limit updated.");
        }

        private void AssertBotAdmin(Context ctx)
        {
            if (!IsBotAdmin(ctx))
                throw new PKError("This command is only usable by bot admins.");
        }

        private bool IsBotAdmin(Context ctx)
        {
            return _botConfig.AdminRole != null && ctx.Member.Roles.Contains(_botConfig.AdminRole.Value);
        }
    }
}