package com.identityblitz.idp.federation.matching.dynamic;

import java.lang.*;
import java.util.*;
import java.text.*;
import java.time.*;
import java.time.format.*;
import java.time.chrono.*;
import java.math.*;
import java.security.*;
import javax.crypto.*;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import com.identityblitz.idp.federation.*;
import com.identityblitz.idp.federation.matching.*;
import com.identityblitz.idp.flow.common.api.*;
import com.identityblitz.idp.flow.common.model.*;
import com.identityblitz.idp.federation.matching.dynamic.*;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.identityblitz.idp.extensions.types.JsObject;


import com.identityblitz.idp.extensions.types.JsObject;
import com.identityblitz.idp.federation.matching.*;
import com.identityblitz.idp.flow.common.api.HttpFactory;

public class Esia_ntc_1Esia_ntc extends MatchingBlock {  
  private final Logger logger = LoggerFactory.getLogger("com.identityblitz.idp.federation.matching.dynamic");
  
  private static Boolean SEARCH_BY_SNILS = true;
  
  private MatchResult tryToSearch(MatchingContext ctx) {
	String extSnils = null;
    if (SEARCH_BY_SNILS && ctx.extAttrs().asString("snils") != null) {
    	extSnils = ctx.extAttrs().asString("snils").replaceAll("([-, ])", "");
    }
	String extPass = ctx.extAttrs().asString("passportSN");

    logger.debug("Bind ESIA, searching by snils = {}, pass = {}, pid = {}", extSnils, extPass, ctx.id());
    MatchingFilter filter = createFilter(extSnils, extPass);
    return find(ctx, filter);
  }

  private MatchingFilter or(MatchingFilter filter, String name, String value) {
      if (value != null){
              if (filter == null){
                  filter = MatchingFilter.empty().eq(name, value);
              } else {
                  filter = filter.or().eq(name, value);
              }
      }
      return filter;
  }

  private MatchingFilter createFilter(String extSnils, String extPass) {
      MatchingFilter filter = null;
      filter = or(filter, "passportSN", extPass);
      filter = or(filter, "snils", extSnils);
      return filter;
  }  

  private String getAttribute(MatchingUser user, String attrName) {
      return user.attrs().asString(attrName);
  }

  private MatchingUser findUser(List<MatchingUser> users, String attrName, String attrValue, MatchingContext ctx) {
    List<MatchingUser> found = new ArrayList<>();
        for (MatchingUser u : users){
            if (attrValue.equals(getAttribute(u, attrName))) found.add(u);
        }
    if (found.isEmpty()) return null;
    else if (found.size() == 1) return found.get(0);
    else {
		logger.debug("Bind ESIA, found '{}' accounts while matching within found users by {} = {}, ESIA oid = {} for reference, pid = {}", found.size(), attrName, attrValue, ctx.extAttrs().asLong("oid"), ctx.id());
		return null;  // too many users found
	}
  }


  private String _mask(final String attr, int f, int l) {
	  final StringBuilder sb = new StringBuilder();
	  int length = attr.length();
	  sb.append(attr.substring(0, f));
	  sb.append(String.join("", Collections.nCopies(length - f - l, "*")));
	  sb.append(attr.substring(length - l, length));
	  return sb.toString();
  }
    
  private JsObject toRegData(JsObject extAttrs, Boolean matchFailed) {
    
    JsObject reg = JsObject.empty;

    return reg;
  }

  private Tuple2<JsObject, Set<String>> toUpdData(JsObject extAttrs, MatchingUser user) {
	logger.debug("Bind ESIA, extAttrs = {}", extAttrs);
    String extEmail = null;
    JsObject emailObj = extAttrs.asObject("email");
    if (emailObj != null && emailObj.asString("value") != null) {
		String email = emailObj.asString("value").toLowerCase();
    }

    JsObject upd = JsObject.empty;
    HashSet<String> del = new HashSet<String>();

    if (extAttrs.asLong("oid") != null) {
      upd = upd.addString("esiaid", Long.toString(extAttrs.asLong("oid")));    
    } else if (getAttribute(user, "esiaid") != null){
      del.add("esiaid");
    }

    if (extEmail != null) {
      upd = upd.addString("mail", extEmail);    
    } else if (getAttribute(user, "mail") != null){
      del.add("mail");
    }
	
    logger.debug("Bind ESIA, update data = {}", upd);
	logger.debug("Bind ESIA, delete data = {}", del);
	
    return change(upd, del);    
  }

  @Override
  public Tuple2<String, String> displayUserData(final String subjectId, final JsObject attrs) {
    String id = "";
    String name = "";
    String mobile = attrs.asString("mobile");
    if (mobile != null) {
      id =_mask(mobile, 4, 2);
    }
    
    if (attrs.asString("FirstName") != null) {
      	name = attrs.asString("FirstName");
    }    
    
    return userIdAndName(id, name);    
  }  
  
/*
Here attributes from ext source are defined and search is initiated  
*/
  
  @Override public MatchResult match(MatchingContext ctx, List<MatchingUser> users){
	String extEmail = null;
    JsObject emailObj = ctx.extAttrs().asObject("email");
    if (emailObj != null && emailObj.asString("value") != null) {
		extEmail = emailObj.asString("value").toLowerCase();
    }
		
    String extSnils = null;
	if (ctx.extAttrs().asString("snils") != null) {
		extSnils = ctx.extAttrs().asString("snils").replaceAll("([-, ])", "");
    }
	
	String extPass = ctx.extAttrs().asString("passportSN");    
	
	String esiaid = Long.toString(ctx.extAttrs().asLong("oid"));
    
	String entryPoint = "";        
	entryPoint = ctx.entryPoint().name();        

	logger.debug("Bind ESIA, extAttrs = {}, pid = {}", ctx.extAttrs(), ctx.id());
    logger.debug("Bind ESIA, ctx.iteration() = {}, pid = {}", ctx.iteration(), ctx.id());
	logger.debug("Bind ESIA, users = {}, pid = {}", users, ctx.id());
    logger.debug("Bind ESIA, snils = {}, pid = {}", extSnils, ctx.id());
    logger.debug("Bind ESIA, passportSN = {}, pid = {}", extPass, ctx.id());
    logger.debug("Bind ESIA, email = {}, pid = {}", extEmail, ctx.id());
	logger.debug("Bind ESIA-Sudir, esiaid = {}, pid = {}", esiaid, ctx.id());
    logger.debug("Bind ESIA-Sudir, entryPoint = {}, pid = {}", entryPoint, ctx.id());

    
    if (ctx.iteration() == 1 && (extSnils != null || extPass != null)) {
      return tryToSearch(ctx);
    }
    
    // Looking for user with snils
    
    if (users.size()>0 && extSnils != null) {
      MatchingUser user = findUser(users, "snils", extSnils, ctx);
      if (user!= null) {
		  if (getAttribute(user, "esiaid") != null && !getAttribute(user, "esiaid").equals(esiaid)) {
				logger.debug("Bind ESIA, user with snils = {} is = {}. But some other esiaid = {} is there, pid = {}. Ext data: passport = {}, id = {}", extSnils, user, getAttribute(user, "esiaid"), ctx.id(), extPass, esiaid);
				return matchError(ctx, new MatchingError("esia_not_matched_already_bound_to_esia", "User found, but it is already bound to another ESIA account"));
		  }
		logger.debug("Bind ESIA, user with subjectId = {} will be bound by snils = {}, esiaid = {}, pid = {}", getAttribute(user, "sub"), extSnils, esiaid, ctx.id());
		 return matched(ctx, user);
      } else {
        logger.debug("Bind ESIA, user with snils = {} not found, pid = {}", extSnils, ctx.id());
      }
    }

    // Looking for user with passport
    
    if (users.size()>0 && extPass != null) {
      MatchingUser user = findUser(users, "passportSN", extPass, ctx);
      if (user!= null) {
        logger.debug("Bind ESIA, user with pass = {} is = {}. Making additional cheсks, pid = {}", extPass, user, ctx.id());
		if (getAttribute(user, "esiaid") != null && !getAttribute(user, "esiaid").equals(esiaid)) {
				logger.debug("Bind ESIA, user with pass = {} found. But some other esiaid = {} is there, pid = {}. Ext data: passport = {}, id = {}", extPass, getAttribute(user, "esiaid"), ctx.id(), extPass, esiaid);
				return matchError(ctx, new MatchingError("esia_not_matched_already_bound_to_esia", "User found, but it is already bound to another ESIA account"));
		}		
    	if (getAttribute(user, "snils") == null) {
			logger.debug("Bind ESIA, user with subjectId = {} with no SNILS will be bound by pass = {}, esiaid = {}, pid = {}", getAttribute(user, "sub"), extPass, esiaid, ctx.id());
        	return matched(ctx, user);
        } else if (extSnils != null && getAttribute(user, "snils").equals(extSnils)) {
			logger.debug("Bind ESIA, user with subjectId = {} with equal SNILS will be bound by pass = {}, esiaid = {}, pid = {}", getAttribute(user, "sub"), extPass, esiaid, ctx.id());
        	return matched(ctx, user);
        } else if (extSnils == null) {
			logger.debug("Bind ESIA, user with subjectId = {} with no EXT SNILS will be bound by pass = {}, esiaid = {}, pid = {}", getAttribute(user, "sub"), extPass, esiaid, ctx.id());
        	return matched(ctx, user);
        } else {
          logger.debug("Bind ESIA, user with pass = {} has snils {}, but in ext account snils is {}. No bind, pid = {}", extPass, getAttribute(user, "snils"), extSnils, ctx.id());
        }
      } else {
        logger.debug("Bind ESIA, user with pass = {} not found, pid = {}", extPass, ctx.id());
      }
    }
    
    return matchError(ctx, new MatchingError("not_matched", "User not found or not matched"));
	
  };
  
  @Override public Tuple2<JsObject, Set<String>> update(JsObject extAttrs, MatchingUser user, Boolean justMatched, HttpFactory httpFactory){    
    return toUpdData(extAttrs, user);
  };
}