using System;
using System.Collections;
using System.Collections.Generic;
using Security;
using Foundation;
using System.Diagnostics;
using System.Linq;
using ObjCRuntime;

namespace [YOURNAMESPACEHERE]
{
    public class PlatformEncryptionKeyHelper
    {
        private readonly bool _shouldSyncAcrossDevices;

        private readonly string _keyName;

        public int KeySize { get; set; } = 2048;

        public PlatformEncryptionKeyHelper(string keyname, bool shouldSyncAcrossDevices = false)
        {
            _keyName = !string.IsNullOrEmpty(keyname) ? keyname.ToLowerInvariant() : $"{nameof(PlatformEncryptionKeyHelper).ToLowerInvariant()}";

            _shouldSyncAcrossDevices = shouldSyncAcrossDevices;
        }

        public bool KeysExist()
        {
            return GetPrivateKey() != null;
        }
        public bool Delete()
        {
            var findExisting = new SecRecord(SecKind.Key)
            {
                ApplicationTag = NSData.FromString(_keyName, NSStringEncoding.UTF8),
                KeyType = SecKeyType.RSA,
                Synchronizable = _shouldSyncAcrossDevices
            };

            SecStatusCode code = SecKeyChain.Remove(findExisting);

            return code == SecStatusCode.Success;
        }

        public SecKey GetPrivateKey()
        {
            var privateKey = SecKeyChain.QueryAsConcreteType(
                new SecRecord(SecKind.Key)
                {
                    ApplicationTag = NSData.FromString(_keyName, NSStringEncoding.UTF8),
                    KeyType = SecKeyType.RSA,
                    Synchronizable = shouldSyncAcrossDevices
                },
                out var code);

            return code == SecStatusCode.Success ? privateKey as SecKey : null;
        }

        public SecKey GetPublicKey()
        {
            return GetPrivateKey()?.GetPublicKey();
        }

        public bool CreatePrivateKey()
        {
            Delete();
            var keyParams = CreateRsaParams();

            SecKey.CreateRandomKey(keyParams, out var keyCreationError);

            if (keyCreationError != null)
            {
                Debug.WriteLine($"{keyCreationError.LocalizedFailureReason}\n{keyCreationError.LocalizedDescription}");
            }

            return keyCreationError == null;
        }
        
        private NSDictionary CreateRsaParams()
        {
            IList<object> keys = new List<object>();
            IList<object> values = new List<object>();

            //creating the private key params
            keys.Add(IosConstants.Instance.KSecAttrApplicationTag);
            keys.Add(IosConstants.Instance.KSecAttrIsPermanent);
            keys.Add(IosConstants.Instance.KSecAttrAccessible);

            values.Add(NSData.FromString(keyName, NSStringEncoding.UTF8));
            values.Add(NSNumber.FromBoolean(true));
            values.Add(IosConstants.Instance.KSecAccessibleWhenUnlocked);

            NSDictionary privateKeyAttributes = NSDictionary.FromObjectsAndKeys(values.ToArray(), keys.ToArray());

            keys.Clear();
            values.Clear();

            //creating the keychain entry params
            //no need for public key params, as it will be created from the private key once it is needed
            keys.Add(IosConstants.Instance.KSecAttrKeyType);
            keys.Add(IosConstants.Instance.KSecAttrKeySize);
            keys.Add(IosConstants.Instance.KSecPrivateKeyAttrs);

            values.Add(IosConstants.Instance.KSecAttrKeyTypeRSA);
            values.Add(NSNumber.FromInt32(this.KeySize));
            values.Add(privateKeyAttributes);

            return NSDictionary.FromObjectsAndKeys(values.ToArray(), keys.ToArray());
        }
    }

    //Xamarin does not expose these values, so we have to pick them up manually
    internal class IosConstants
    {
        private static IosConstants _instance;

        public static IosConstants Instance => _instance ?? (_instance = new IosConstants());

        public readonly NSString KSecAttrKeyType;
        public readonly NSString KSecAttrKeySize;
        public readonly NSString KSecAttrKeyTypeRSA;
        public readonly NSString KSecAttrIsPermanent;
        public readonly NSString KSecAttrApplicationTag;
        public readonly NSString KSecPrivateKeyAttrs;
        public readonly NSString KSecClass;
        public readonly NSString KSecClassKey;
        public readonly NSString KSecPaddingPKCS1;
        public readonly NSString KSecAccessibleWhenUnlocked;
        public readonly NSString KSecAttrAccessible;

        public IosConstants()
        {
            var handle = Dlfcn.dlopen(Constants.SecurityLibrary, 0);

            try
            {
                KSecAttrApplicationTag = Dlfcn.GetStringConstant(handle, "kSecAttrApplicationTag");
                KSecAttrKeyType = Dlfcn.GetStringConstant(handle, "kSecAttrKeyType");
                KSecAttrKeyTypeRSA = Dlfcn.GetStringConstant(handle, "kSecAttrKeyTypeRSA");
                KSecAttrKeySize = Dlfcn.GetStringConstant(handle, "kSecAttrKeySizeInBits");
                KSecAttrIsPermanent = Dlfcn.GetStringConstant(handle, "kSecAttrIsPermanent");
                KSecPrivateKeyAttrs = Dlfcn.GetStringConstant(handle, "kSecPrivateKeyAttrs");
                KSecClass = Dlfcn.GetStringConstant(handle, "kSecClass");
                KSecClassKey = Dlfcn.GetStringConstant(handle, "kSecClassKey");
                KSecPaddingPKCS1 = Dlfcn.GetStringConstant(handle, "kSecPaddingPKCS1");
                KSecAccessibleWhenUnlocked = Dlfcn.GetStringConstant(handle, "kSecAttrAccessibleWhenUnlocked");
                KSecAttrAccessible = Dlfcn.GetStringConstant(handle, "kSecAttrAccessible");

            }
            finally
            {
                Dlfcn.dlclose(handle);
            }
        }
    }
}