using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Xml;
using Valg.EVA.Skanning.Contracts.Security;
using Valg.EVA.Skanning.Identity.Area;

namespace Valg.EVA.Skanning.Identity
{
    public class TokenParser
    {
        public TokenParser(string xmlToken)
        { 
            Initialize();
            ParseToken(xmlToken);
        }

        public List<Claim> Claims { get; private set; }

        public List<Role> Roles { get; private set; }

        private void ParseToken(string xmlToken)
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xmlToken);

            AddElectionAndUserClaims(xmlDoc);

            AddEmergencyModeClaims(xmlDoc);

            AddRoleAndAccessObjectClaims(xmlDoc);
        }

        private void AddEmergencyModeClaims(XmlDocument xmlDoc)
        {
            XmlNode isEmergencyModeClaimNode = xmlDoc.SelectSingleNode("/token/isEmergencyMode");
            if (isEmergencyModeClaimNode != null)
            {
                AddClaim(new Claim(ClaimDefinitions.IsEmergencyModeClaimType, isEmergencyModeClaimNode.InnerText));
            }            
        }

        private void Initialize()
        {
            Claims = new List<Claim>();
            this.Roles = new List<Role>();
        }

        private void AddElectionAndUserClaims(XmlDocument xmlDoc)
        {
            AddClaim(CreateNewClaim(xmlDoc, ClaimDefinitions.TokenUidClaimType, "/token/uid"));
            AddClaim(CreateNewClaim(xmlDoc, ClaimDefinitions.OperatorNameClaimType, "/token/operatorName"));
            AddClaim(CreateNewClaim(xmlDoc, ClaimDefinitions.ElectionEventIdClaimType, "/token/electionEventId"));
            AddClaim(CreateNewClaim(xmlDoc, ClaimDefinitions.TokenCreatedClaimType, "/token/created"));
            AddClaim(CreateNewClaim(xmlDoc, ClaimDefinitions.TokenExpirationClaimType, "/token/expiration"));
            AddClaim(CreateNewClaim(xmlDoc, ClaimDefinitions.ElectionEventNameClaimType, "/token/electionEventName"));
            
        }

        private void AddRoleAndAccessObjectClaims(XmlDocument xmlDoc)
        {
            XmlNodeList roles = xmlDoc.SelectNodes("/token/roles/role");
            foreach (XmlNode roleNode in roles)
            {
                string tokenAreaContextId = roleNode.SelectSingleNode("areaContextId").InnerText;
                var area = new AreaDescription(tokenAreaContextId);
                if (!area.Level.ShouldAddRoles())
                    continue;

                Role newRole = new Role();
                bool accessObjectClaimAdded = AddAccessObjectClaims(roleNode, tokenAreaContextId, newRole);

                if (accessObjectClaimAdded)
                {
                    AddRoleAreaContext(area, newRole);
                    SetRoleProperties(roleNode, newRole);
                    this.Roles.Add(newRole);
                }
            }
        }

        private void AddRoleAreaContext(AreaDescription area, Role role)
        {
            
            role.Area = area;
            role.CountyArea = area;
            
        }

        private bool AddAccessObjectClaims(XmlNode roleNode, string tokenAreaContextId, Role role)
        {
            bool accessObjectClaimAdded = false;

            foreach (var claimType in ClaimDefinitions.EvaAccessClaimTypes)
            {
                string xpath = string.Format("accesses/access[text()=\"{0}\"]", claimType);
                XmlNode accessNode = roleNode.SelectSingleNode(xpath);

                if (accessNode != null)
                {
                    var accessClaim = new Claim(claimType, tokenAreaContextId);
                    AddClaim(accessClaim);
                    role.ClaimsInRole.Add(accessClaim);
                    accessObjectClaimAdded = true;
                }
            }

            return accessObjectClaimAdded;
        }

        private void SetRoleProperties(XmlNode roleNode, Role role)
        {
            role.RoleName = roleNode.SelectSingleNode("roleName").InnerText;
            role.RoleId = roleNode.SelectSingleNode("roleId").InnerText;
        }

        private Claim CreateNewClaim(XmlNode roleNode, string typeName, string xPath)
        {
            XmlNode claimNode = roleNode.SelectSingleNode(xPath);
            return new Claim(typeName, claimNode.InnerText);
        }

        private void AddClaim(Claim newClaim)
        {
            if (!ClaimAlreadyExist(newClaim))
            {
                this.Claims.Add(newClaim);
            }
        }

        private bool ClaimAlreadyExist(Claim newClaim)
        {
            return this.Claims.Any(p => p.Type == newClaim.Type && p.Value == newClaim.Value);
        }
    }
}
