using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using Valg.EVA.Skanning.Contracts.Security;
using Valg.EVA.Skanning.Identity.Area;
using Valg.EVA.Skanning.ServicesModel.ElectionConfig.DataProvider;
using Valg.EVA.Skanning.ServicesModel.ElectionConfig.Model;

namespace Valg.EVA.Skanning.Identity
{
    public class ClaimsAndRolesBuilder : IClaimsAndRolesBuilder
    {
        private readonly IMunicipalityDataProvider municipalityDataProvider;
        private readonly IIdentityProvider identityProvider;
        private readonly IElectionConfigDataProvider electionConfigDataProvider;

        public ClaimsAndRolesBuilder(IIdentityProvider identityProvider, IElectionConfigDataProvider electionConfigDataProvider, IMunicipalityDataProvider municipalityDataProvider)
        {
            this.identityProvider = identityProvider;
            this.electionConfigDataProvider = electionConfigDataProvider;
            this.municipalityDataProvider = municipalityDataProvider;
        }

        public void ParseToken(byte[] token, byte[] signature)
        {
            ParseAndUpdateIdentityProvider(token, signature);
            FetchMetadataAndCreateAncillaryRoles();
            SetActiveRole();
        }

        private void SetActiveRole()
        {
            if (identityProvider.Roles.Count == 1)
            {
                identityProvider.SetCurrentRole(identityProvider.Roles.First());
            }
        }

        private void FetchMetadataAndCreateAncillaryRoles()
        {
            var electionEventId = identityProvider.ClaimValue(ClaimDefinitions.ElectionEventIdClaimType);
            var existingRolesFromToken = identityProvider.Roles.ToArray();

            foreach (var roleInToken in existingRolesFromToken)
            {
                SetReportingUnitIdentifier(roleInToken, electionEventId);

                UpdateAreaName(roleInToken, electionEventId);

                UpdateRoleWithContestDataAndCreateAncillaryRolesInContest(roleInToken, electionEventId);

                CreateAncillaryRolesForSubAreas(roleInToken, electionEventId);
            }
        }

        private void CreateAncillaryRolesForSubAreas(Role role, string electionEventId)
        {
            if (role.Area.Level == AreaDescription.ElectionLevel.County)
            {
                var additionalRoles = new List<Role>();
                var muncipalites = municipalityDataProvider.GetMunicipalitiesInCounty(electionEventId, role.Area.CountyCode);
                foreach (var municipality in muncipalites)
                {
                    var newAreaContextId = $"{role.Area.TokenAreaContextId}.{municipality.Code}";
                    var areaDescripton = new AreaDescription(newAreaContextId);
                    if (areaDescripton.IsValid)
                    {
                        var newRole = new Role
                        {
                            Area = role.Area,
                            ContestId = role.ContestId,
                            ContestName = role.ContestName,
                            CountyArea = role.CountyArea,
                            ElectionCategory = role.ElectionCategory,
                            RoleId = role.RoleId,
                            ReportingUnitIdentifier = role.ReportingUnitIdentifier,
                            ElectionGroup = role.ElectionGroup,
                            ElectionGroupName = role.ElectionGroupName,
                            ElectionIdentifier = role.ElectionIdentifier,
                            ElectionName = role.ElectionName,
                            IsSubArea = role.IsSubArea,
                            RoleName = role.RoleName,
                            UserId = role.UserId
                        };

                        newRole.ClaimsInRole.AddRange(role.ClaimsInRole);
                        newRole.Area = areaDescripton;
                        newRole.CountyArea = role.Area;
                        SetClaimsWithAreaContextValueToNewAreaContext(newRole, newAreaContextId);
                        newRole.Area.AreaName = municipality.Name;
                        newRole.IsSubArea = true;
                        additionalRoles.Add(newRole);
                    }
                    else
                    {
                        throw new System.ArgumentException("Area context for ancillary roles for sub areas is invalid");
                    }
                }
                identityProvider.AddRoles(additionalRoles);
            }
        }

        private void SetClaimsWithAreaContextValueToNewAreaContext(Role role, string newAreaContextId)
        {
            foreach (var existingClaim in role.ClaimsInRole.ToArray())
            {
                if (IsAreaContextId(existingClaim.Value))
                {
                    var newClaim = new Claim(existingClaim.Type, newAreaContextId);
                    role.ClaimsInRole.Remove(existingClaim);
                    role.ClaimsInRole.Add(newClaim);
                }
            }
        }

        private bool IsAreaContextId(string value)
        {
            return new AreaDescription(value).IsValid;
        }

        private void SetReportingUnitIdentifier(Role role, string electionEventId)
        {
            string areaContextIdOnReportingUnit;
            if (role.Area.Level == AreaDescription.ElectionLevel.Borough)
            {
                AreaDescription muncipalityArea = new AreaDescription(role.Area.ConvertAreaContextId(AreaDescription.ElectionLevel.Municipality));
                areaContextIdOnReportingUnit = muncipalityArea.EmlAreaContextId;
            }
            else
            {
                areaContextIdOnReportingUnit = role.Area.EmlAreaContextId;
            }

            var reportingUnit = electionConfigDataProvider.GetReportingUnit(electionEventId, areaContextIdOnReportingUnit);
            role.ReportingUnitIdentifier = reportingUnit.Id;
        }

        private void UpdateAreaName(Role role, string electionEventId)
        {
            role.Area.AreaName = municipalityDataProvider.GetAreaName(electionEventId, role.CountingEmlAreaContextId);
        }

        private void UpdateRoleWithContestDataAndCreateAncillaryRolesInContest(Role role, string electionEventId)
        {
            var contestsForArea = GetContestForArea(electionEventId, role.Area);

            var additionalRoles = new List<Role>();
            bool isFirst = true;
            foreach (var contest in contestsForArea)
            {
                if (isFirst)
                {
                    UpdateRole(role, contest, electionEventId);
                    isFirst = false;
                }
                else
                {
                    Role newRole = new Role
                    {
                        ClaimsInRole = role.ClaimsInRole,
                        Area = role.Area,
                        RoleName = role.RoleName,
                        ReportingUnitIdentifier = role.ReportingUnitIdentifier,
                        RoleId = role.RoleId
                    };
                    UpdateRole(newRole, contest, electionEventId);
                    additionalRoles.Add(newRole);
                }
            }
            identityProvider.AddRoles(additionalRoles);
        }

        private void UpdateRole(Role role, Contest contest, string electionEventId)
        {
            role.ContestName = contest.Name;
            role.ContestId = contest.Id;

            var election = GetElection(electionEventId, contest.Id);
            role.ElectionName = election.Name;
            role.ElectionIdentifier = election.Id;
            role.ElectionGroup = election.GroupId;
            role.ElectionGroupName = election.GroupName;
            role.ElectionCategory = election.Category;
        }

        private Election GetElection(string electionEventId, string contestId)
        {
            return electionConfigDataProvider.GetElectionByContestId(electionEventId, contestId);
        }

        private List<Contest> GetContestForArea(string electionEventId, AreaDescription areaDescription)
        {
            var contestForArea = electionConfigDataProvider.GetContestsInArea(electionEventId, areaDescription.EmlAreaContextId);
            if (areaDescription.Level == AreaDescription.ElectionLevel.Municipality)
            {
                var countyAreaDescription = new AreaDescription(areaDescription.ConvertAreaContextId(AreaDescription.ElectionLevel.County));
                var contestsForAreasCounty = electionConfigDataProvider.GetContestsInArea(electionEventId, countyAreaDescription.EmlAreaContextId);
                contestForArea.AddRange(contestsForAreasCounty);
            }
            return contestForArea;
        }

        private void ParseAndUpdateIdentityProvider(byte[] token, byte[] signature)
        {
            var parser = new TokenParser(Encoding.UTF8.GetString(token));
            identityProvider.SetToken(token, signature);
            identityProvider.AddClaims(parser.Claims);
            identityProvider.AddRoles(parser.Roles);
        }
    }
}
