using Google.Protobuf;
using Hirs.Pb; // Imports ProvisionerTpm2.proto (compiled and generated by protobuf)
using Serilog;
using System;
using System.Buffers.Text;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace hirs {
    public class Client : IHirsAcaClient {
        public static readonly string POST_IDENTITY_CLAIM_PATH = "HIRS_AttestationCA/identity-claim-tpm2/process";
        public static readonly string POST_REQUEST_CERT_TPM2_PATH = "HIRS_AttestationCA/request-certificate-tpm2";

        private readonly Uri uri;
        private static readonly HttpClientHandler handler = new() {
            ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
        }; // TODO: Overhaul ACA security 
        private readonly HttpClient client;

        /**
         * This method will create an HttpClient that will accept any server certificate.
         */
        public Client(string address) {
            uri = new(address);
            client = new HttpClient(handler);
        }

        public Client(string address, HttpClient httpClient) {
            uri = new(address);
            client = httpClient;
        }

        public async Task<IdentityClaimResponse> PostIdentityClaim(IdentityClaim identityClaim) {
            MemoryStream stream = new(identityClaim.ToByteArray());
            // serialize to stream

            stream.Seek(0, SeekOrigin.Begin);
            Uri full_address = new(uri.AbsoluteUri + POST_IDENTITY_CLAIM_PATH);
            // send data via HTTP
            StreamContent streamContent = new(stream);
            streamContent.Headers.TryAddWithoutValidation("Content-Type", "application/octet-stream");
            streamContent.Headers.TryAddWithoutValidation("Accept", "application/octet-stream, application/json");

            IdentityClaimResponse icr = null;
            try {
                Log.Debug("Attempting to send IdentityClaim to " + full_address);
                HttpResponseMessage response = await client.PostAsync(full_address, streamContent).ConfigureAwait(continueOnCapturedContext: false);
                Log.Debug(response.ToString());
                if (response.StatusCode == HttpStatusCode.OK) {
                    byte[] contentBytes = await response.Content.ReadAsByteArrayAsync();
                    icr = IdentityClaimResponse.Parser.ParseFrom(contentBytes);
                    Log.Debug("IdentityClaim delivery succeeded.");
                } else {
                    Log.Debug("IdentityClaim delivery failed.");
                    Log.Debug("Request reason phrase: " + response.ReasonPhrase);
                    Log.Debug("Request content: " + response.Content);
                }
            } catch (Exception e) {
                Log.Debug(e, "Error during post of the identity claim.");
            }
            return icr;
        }

        public IdentityClaim CreateIdentityClaim(DeviceInfo dv, byte[] akPublicArea, byte[] ekPublicArea,
                                       byte[] endorsementCredential, List<byte[]> platformCredentials,
                                       string paccoroutput, byte[] ldevidPublicArea = null) {
            IdentityClaim identityClaim = new();
            identityClaim.Dv = dv;
            identityClaim.AkPublicArea = ByteString.CopyFrom(akPublicArea);
            identityClaim.EkPublicArea = ByteString.CopyFrom(ekPublicArea);
            identityClaim.EndorsementCredential = ByteString.CopyFrom(endorsementCredential);
            if (platformCredentials != null) {
                foreach (byte[] platformCertificate in platformCredentials) {
                    identityClaim.PlatformCredential.Add(ByteString.CopyFrom(platformCertificate));
                }
            }
            identityClaim.PaccorOutput = paccoroutput;
            if (ldevidPublicArea != null) identityClaim.LdevidPublicArea = ByteString.CopyFrom(ldevidPublicArea);
            
            return identityClaim;
        }

        public async Task<CertificateResponse> PostCertificateRequest(CertificateRequest certReq) {
            MemoryStream stream = new(certReq.ToByteArray());
            // serialize to stream

            stream.Seek(0, SeekOrigin.Begin);
            Uri full_address = new(uri.AbsoluteUri + POST_REQUEST_CERT_TPM2_PATH);
            // send data via HTTP
            StreamContent streamContent = new(stream);
            streamContent.Headers.TryAddWithoutValidation("Content-Type", "application/octet-stream");
            streamContent.Headers.TryAddWithoutValidation("Accept", "application/octet-stream, application/json");

            CertificateResponse cr = null;
            try {
                Log.Debug("Attempting to send the Certificate Request to " + full_address);
                HttpResponseMessage response = await client.PostAsync(full_address, streamContent).ConfigureAwait(continueOnCapturedContext: false);
                Log.Debug(response.ToString());
                if (response.StatusCode == HttpStatusCode.OK) {
                    byte[] contentBytes = await response.Content.ReadAsByteArrayAsync();
                    cr = CertificateResponse.Parser.ParseFrom(contentBytes);
                    Log.Debug("Certificate Response recevied.");
                } else {
                    Log.Debug("Certificate Response failed.");
                    Log.Debug("Request reason phrase: " + response.ReasonPhrase);
                    Log.Debug("Request content: " + response.Content);
                }
            } catch (Exception e) {
                Log.Debug(e, "Error during post of the certificate request.");
            }
            return cr;
        }

        public CertificateRequest CreateAkCertificateRequest(byte[] secret, CommandTpmQuoteResponse ctqr) {
            CertificateRequest akCertReq = new();
            akCertReq.Nonce = ByteString.CopyFrom(secret);
            CommandTpmQuoteResponse.formatQuoteInfoSigForAca(ctqr.quoted, ctqr.signature, out string quoteInfoSigStr);
            akCertReq.Quote = ByteString.CopyFromUtf8(quoteInfoSigStr);
            //formatPcrValuesForAca(pcrValues, out string pcrValuesStr);
            //akCertReq.Pcrslist = ByteString.CopyFromUtf8(pcrValuesStr);

            return akCertReq;
        }
    }
}
