package in.gov.digilocker.uidai.services.client;

import java.security.KeyStore;
import java.util.Base64;
import java.util.Map.Entry;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import in.gov.digilocker.uidai.logs.fpmodal.BiomatricData;
import in.gov.digilocker.uidai.logs.modal.OTPAuth;
import in.gov.digilocker.uidai.services.docsigner.DocSigner;
import in.gov.digilocker.uidai.services.properties.OTPAuthProperties;
import in.gov.digilocker.uidai.services.properties.UIDAIProps;
import in.gov.digilocker.uidai.services.properties.UidaiErrors;
import in.gov.digilocker.uidai.services.resources.PostData;
import in.gov.digilocker.uidai.services.resources.RDEncrypter;
import in.gov.digilocker.uidai.services.resources.Utilities;
import in.gov.digilocker.uidai.services.xmlresponse.AuthResponse;
import in.gov.digilocker.uidai.web.properties.DigilockerProperties;

@Service
public class AuthClient {
	
	public AuthClient() {
		// TODO Auto-generated constructor stub
	}
	
	private static final Logger logger= LoggerFactory.getLogger(AuthClient.class);	
	
	public AuthResponse verifyAuthenticationUsingOTP(UIDAIProps uidaiProps, DigilockerProperties digilockerProps, OTPAuthProperties authProperties, UidaiErrors uidaiErrors,  OTPAuth otpAuth, String reqTimeStamp) throws RuntimeException, Exception {		
		//logger.info("OTP validation using auth 2.5 process started.");
		try {
			
			String contentType = "application/xml";
			
			RDEncrypter enc = new RDEncrypter(uidaiProps.getUidaiCertPath());
			
			KeyStore.PrivateKeyEntry keyEntry = enc.getKeyFromKeyStore(digilockerProps.getDigilockerCertPath(), digilockerProps.getDigilockerCertPassword(), digilockerProps.getDigilockerCertAlias());
			
			if (keyEntry == null) {
				throw new RuntimeException("ERR-107, KeyFinder.getKeyFromKeyStore has returned null value.");
			}
			
			String pidxml= generatePIDBlock(otpAuth.getOtp(), reqTimeStamp, uidaiProps.getUidaiPIDVer(), "");			
			byte[] sessionKey = enc.generateSessionKey();
			byte[] encryptedSessionKey = enc.encryptUsingPublicKey(sessionKey);
			byte[] pidXmlBytes = pidxml.getBytes(uidaiProps.getEncodingType());
			byte[] encXMLPIDData = enc.encrypt(pidXmlBytes, sessionKey, reqTimeStamp);
			byte[] iv = enc.generateIv(reqTimeStamp);
			byte[] aad = enc.generateAad(reqTimeStamp);
			byte[] hmac = enc.generateHash(pidXmlBytes);
			byte[] encryptedHmacBytes = enc.encryptDecryptUsingSessionKey(true, sessionKey, iv, aad, hmac);		      
			String certificate_expiry = enc.getCertificateIdentifier();
			
			String authXML= createOTPAuthRequest(uidaiProps, authProperties, otpAuth.getAadhaarNumber(), otpAuth.getUniqueReqId(), otpAuth.getConsent(), certificate_expiry, encryptedSessionKey, encryptedHmacBytes, encXMLPIDData); 
			
			String signedAuthXML= DocSigner.signXML(authXML, true, keyEntry);		
			
			Utilities.checkProxy(digilockerProps.isProxy(), digilockerProps.getProxyHost(), digilockerProps.getProxyPort());			
			
			String respXML= new PostData().excutePostXml(uidaiProps.getUidaiServiceUrl(), signedAuthXML, contentType);
			
			AuthResponse authResponse= null!= respXML && !"".equals(respXML) ? AuthResponse.buildAuthResponse(respXML): new AuthResponse();			
			
			if ("y".equalsIgnoreCase(authResponse.getRet())) {				
				logger.info("User authentication process using OTP is completed successfully, for more details please see the log.");				
			} else {
				for (Entry<String, String> entry : uidaiErrors.getErrorMap().entrySet()) {
		            if (entry.getKey().trim().equals(authResponse.getErr().trim())) {
		            	authResponse.setMsg(entry.getValue().trim());		            				                
		            }
		        }
		        logger.info("Authentication using OTP is failed due to, " + authResponse.getMsg() + ", for more details please see the log.");
			}
			authResponse.setXmlResp(respXML);
			return authResponse;
		} catch (RuntimeException e) {
			e.printStackTrace();
			logger.info("Runtime exception in AuthClient.verifyOTPUsingAuth:- " +e.getMessage());
			throw e;
		} catch (Exception e) {
			e.printStackTrace();
			logger.info("Main exception in AuthClient.verifyOTPUsingAuth:- " +e.getMessage());
			throw e;
		}		
	}
	
	
	public String generatePIDBlock(String otp, String ts, String pidVer, String wadh) throws Exception {
		DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
		
		Document document = docBuilder.newDocument();
		document.setXmlVersion("1.0");
		document.setXmlStandalone(true);
		
		Element rootElement = document.createElement("Pid");
		Attr tsAttr = document.createAttribute("ts");
		tsAttr.setValue(ts);
		rootElement.setAttributeNode(tsAttr);
		
		Attr verAttr = document.createAttribute("ver");
		verAttr.setValue(pidVer);
		rootElement.setAttributeNode(verAttr);

		Attr wadhAttr = document.createAttribute("wadh");
		wadhAttr.setValue((wadh != null) && (!"".equals(wadh)) ? wadh : "");
		rootElement.setAttributeNode(wadhAttr);
		
		Element pvElement = document.createElement("Pv");
		pvElement.setAttribute("otp", otp);
		rootElement.appendChild(pvElement);
		
		document.appendChild(rootElement);
		String pidXML = Utilities.convertDocumentToString(document);
		return pidXML;
	}	
	
	public String createOTPAuthRequest(UIDAIProps uidaiProps, OTPAuthProperties authProperties, String uid, String unique_req_id, String rc, String certificate_expiry, byte[] encryptedSessionKey, byte[] encryptedHmacBytes, byte[] encXMLPIDData) throws Exception {
		
		DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
		
		Document document = docBuilder.newDocument();
		document.setXmlVersion("1.0");
		document.setXmlStandalone(true);
		
		Element rootElement = document.createElement("Auth");
		
		Attr uidAttr = document.createAttribute("uid");
		uidAttr.setValue(uid.trim());
		rootElement.setAttributeNode(uidAttr);
		
		Attr rcAttr = document.createAttribute("rc");
		rcAttr.setValue(rc.trim());
		rootElement.setAttributeNode(rcAttr);
		
		Attr tidAttr = document.createAttribute("tid");
		tidAttr.setValue(authProperties.getTid());
		rootElement.setAttributeNode(tidAttr);
		
		Attr acAttr = document.createAttribute("ac");
		acAttr.setValue(authProperties.getAc());
		rootElement.setAttributeNode(acAttr);
		
		Attr saAttr = document.createAttribute("sa");
		saAttr.setValue(authProperties.getSa());
		rootElement.setAttributeNode(saAttr);
		
		Attr verAttr = document.createAttribute("ver");
		verAttr.setValue(authProperties.getOtpAutVersion());
		rootElement.setAttributeNode(verAttr);
		
		Attr txnAttr = document.createAttribute("txn");
		txnAttr.setValue(unique_req_id);
		rootElement.setAttributeNode(txnAttr);
		
		Attr lkAttr = document.createAttribute("lk");
		lkAttr.setValue(authProperties.getLk());
		rootElement.setAttributeNode(lkAttr);
		
		Element usesElement = document.createElement("Uses");
		usesElement.setAttribute("pi", authProperties.getPiUses());
		usesElement.setAttribute("pa", authProperties.getPaUses());
		usesElement.setAttribute("pfa", authProperties.getPfaUses());
		usesElement.setAttribute("bio", authProperties.getBioUses());
		usesElement.setAttribute("pin", authProperties.getPinUses());
		usesElement.setAttribute("otp", authProperties.getOtpUses());
		rootElement.appendChild(usesElement);
		
		
		Element deviceElement = document.createElement("Device");
		deviceElement.setAttribute("udc", authProperties.getUdc());
		deviceElement.setAttribute("rdsId", authProperties.getRdsId());
		deviceElement.setAttribute("rdsVer", authProperties.getRdsVer());
		deviceElement.setAttribute("dpId", authProperties.getDpId());
		deviceElement.setAttribute("dc", authProperties.getDc());
		deviceElement.setAttribute("mi", authProperties.getMi());
		deviceElement.setAttribute("mc", authProperties.getMc());
		rootElement.appendChild(deviceElement);
	    
	    Element skeyElement = document.createElement("Skey");    
	    skeyElement.setAttribute("ci", certificate_expiry);
	    skeyElement.setTextContent(Base64.getEncoder().encodeToString(encryptedSessionKey));
	    rootElement.appendChild(skeyElement);
		    
	    Element hmacElement = document.createElement("Hmac");
	    hmacElement.setTextContent(Base64.getEncoder().encodeToString(encryptedHmacBytes));
	    rootElement.appendChild(hmacElement);
	    
	    Element dataElement = document.createElement("Data");
	    dataElement.setAttribute("type", authProperties.getDataType());
	    dataElement.setTextContent(Base64.getEncoder().encodeToString(encXMLPIDData));
	    rootElement.appendChild(dataElement);
	    
	    document.appendChild(rootElement);
	    
	    return Utilities.convertDocumentToString(document);
	}
	
	
	
	public AuthResponse verifyAuthenticationUsingFingerPrint(UIDAIProps uidaiProps, DigilockerProperties digilockerProps, OTPAuthProperties authProperties, UidaiErrors uidaiErrors, OTPAuth otpAuth, String reqTimeStamp, BiomatricData biomatricData)throws RuntimeException, Exception {		
		//logger.info("FP validation using auth 2.5 process started.");
		try {
			
			String contentType = "application/xml";					
			
			authProperties.setBioUses("y");
			authProperties.setOtpUses("n");
			authProperties.setBtUses("FMR");
			authProperties.setTid("registered");
			
			RDEncrypter enc = new RDEncrypter(uidaiProps.getUidaiCertPath());
			KeyStore.PrivateKeyEntry keyEntry = enc.getKeyFromKeyStore(digilockerProps.getDigilockerCertPath(), digilockerProps.getDigilockerCertPassword(), digilockerProps.getDigilockerCertAlias());
			if (keyEntry == null) {
				throw new RuntimeException("ERR-107, KeyFinder.getKeyFromKeyStore has returned null value.");
			}			
			
			String authXML= createFPAuthRequest(digilockerProps, uidaiProps, authProperties, otpAuth.getAadhaarNumber(), otpAuth.getUniqueReqId(), otpAuth.getConsent(), biomatricData);
			
			String signedAuthXML= DocSigner.signXML(authXML, true, keyEntry);
			
			if (digilockerProps.isExceptionEnabled()) {
				logger.info("OTP AUTH Request XML is:- " + authXML);
			}		
			
			Utilities.checkProxy(digilockerProps.isProxy(), digilockerProps.getProxyHost(), digilockerProps.getProxyPort());			
			
			String respXML= new PostData().excutePostXml(uidaiProps.getUidaiServiceUrl(), signedAuthXML, contentType);
			
			AuthResponse authResponse= null!= respXML && !"".equals(respXML) ? AuthResponse.buildAuthResponse(respXML): new AuthResponse();			
			
			if ("y".equalsIgnoreCase(authResponse.getRet())) {				
				logger.info("User authentication process using OTP is completed successfully, for more details please see the log.");				
			} else {
				for (Entry<String, String> entry : uidaiErrors.getErrorMap().entrySet()) {
		            if (entry.getKey().trim().equals(authResponse.getErr().trim())) {
		            	authResponse.setMsg(entry.getValue().trim());		            				                
		            }
		        }
		        logger.info("Authentication using OTP is failed due to, " + authResponse.getMsg() + ", for more details please see the log.");
			}
			
			authResponse.setXmlResp(respXML);
			
			return authResponse;
		} catch (RuntimeException e) {
			logger.info("Runtime exception in AuthClient.validateOTPUsingAuth:- " +e.getMessage());
			throw e;
		} catch (Exception e) {
			logger.info("Main exception in AuthClient.validateOTPUsingAuth:- " +e.getMessage());
			throw e;
		}		
	}
	
	
	public String createFPAuthRequest(DigilockerProperties digilockerProps, UIDAIProps uidaiProps, OTPAuthProperties authProperties, String uid, String unique_req_id, String rc, BiomatricData biomatricData) throws Exception {
		
		DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
		
		Document document = docBuilder.newDocument();
		document.setXmlVersion("1.0");
		document.setXmlStandalone(true);
		
		Element rootElement = document.createElement("Auth");
		
		Attr uidAttr = document.createAttribute("uid");
		uidAttr.setValue(uid.trim());
		rootElement.setAttributeNode(uidAttr);
		
		Attr rcAttr = document.createAttribute("rc");
		rcAttr.setValue(rc.trim());
		rootElement.setAttributeNode(rcAttr);
		
		Attr tidAttr = document.createAttribute("tid");
		tidAttr.setValue(authProperties.getTid());
		rootElement.setAttributeNode(tidAttr);
		
		Attr acAttr = document.createAttribute("ac");
		acAttr.setValue(authProperties.getAc());
		rootElement.setAttributeNode(acAttr);
		
		Attr saAttr = document.createAttribute("sa");
		saAttr.setValue(authProperties.getSa());
		rootElement.setAttributeNode(saAttr);
		
		Attr verAttr = document.createAttribute("ver");
		verAttr.setValue(authProperties.getOtpAutVersion());
		rootElement.setAttributeNode(verAttr);
		
		Attr txnAttr = document.createAttribute("txn");
		txnAttr.setValue(unique_req_id);
		rootElement.setAttributeNode(txnAttr);
		
		Attr lkAttr = document.createAttribute("lk");
		lkAttr.setValue(authProperties.getLk());
		rootElement.setAttributeNode(lkAttr);
		
		Element usesElement = document.createElement("Uses");
		usesElement.setAttribute("pi", authProperties.getPiUses());
		usesElement.setAttribute("pa", authProperties.getPaUses());
		usesElement.setAttribute("pfa", authProperties.getPfaUses());
		usesElement.setAttribute("bio", authProperties.getBioUses());
		usesElement.setAttribute("bt", authProperties.getBtUses());
		usesElement.setAttribute("pin", authProperties.getPinUses());
		usesElement.setAttribute("otp", authProperties.getOtpUses());
		rootElement.appendChild(usesElement);
		
		
		Element deviceElement = document.createElement("Device");
		deviceElement.setAttribute("rdsId", authProperties.getUdc());
		deviceElement.setAttribute("rdsId", biomatricData.getDeviceInfo().getRdsId());
		deviceElement.setAttribute("rdsVer", biomatricData.getDeviceInfo().getRdsVer());
		deviceElement.setAttribute("dpId", biomatricData.getDeviceInfo().getDpId());
		deviceElement.setAttribute("dc", biomatricData.getDeviceInfo().getDc());
		deviceElement.setAttribute("mi", biomatricData.getDeviceInfo().getMi());
		deviceElement.setAttribute("mc", biomatricData.getDeviceInfo().getMc());
		
		rootElement.appendChild(deviceElement);
		
	    
	    Element skeyElement = document.createElement("Skey");    
	    skeyElement.setAttribute("ci", biomatricData.getSkey().getCi());
	    skeyElement.setTextContent(biomatricData.getSkey().getsKeyDescription());
	    rootElement.appendChild(skeyElement);
		    
	    Element hmacElement = document.createElement("Hmac");
	    hmacElement.setTextContent(biomatricData.getHmac());
	    rootElement.appendChild(hmacElement);
	    
	    Element dataElement = document.createElement("Data");
	    dataElement.setAttribute("type", authProperties.getDataType());
	    dataElement.setTextContent(biomatricData.getData().getDataDescription() );
	    rootElement.appendChild(dataElement);
	    
	    document.appendChild(rootElement);
	    
	    if (digilockerProps.isExceptionEnabled()) {	    
	    	logger.info("Auth Data is:--------"+Utilities.convertDocumentToString(document));
	    }
	    
	    return Utilities.convertDocumentToString(document);
	}
	
	
	
}
