import java.net.URI;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.security.MessageDigest;
import java.time.Duration;
import java.util.*;

/** Java 17+, one recipient request, preview by default. No automatic retries. */
public class BeneficiaryRequest {
    static String quote(String s) {
        StringBuilder b=new StringBuilder("\"");
        for(char c:s.toCharArray()) switch(c) {
            case '"' -> b.append("\\\""); case '\\' -> b.append("\\\\");
            default -> {if(c<32)b.append(String.format("\\u%04x",(int)c));else b.append(c);}
        }
        return b.append('"').toString();
    }
    static String json(Map<String,String> b) {
        StringJoiner j=new StringJoiner(",","{","}");b.forEach((k,v)->j.add(quote(k)+":"+(k.equals("version")?v:quote(v))));return j.toString();
    }
    static Map<String,String> sign(Map<String,String> b,String app,String secret,String timestamp,String nonce) throws Exception {
        if(b.keySet().stream().anyMatch(Set.of("appId","nonce","timestamp","sign")::contains))throw new IllegalArgumentException();
        Map<String,String> h=new TreeMap<>();h.put("appId",app);h.put("timestamp",timestamp);h.put("nonce",nonce);
        TreeMap<String,String> all=new TreeMap<>(b);all.putAll(h);StringJoiner s=new StringJoiner("&");all.forEach((k,v)->s.add(k+"="+v));
        h.put("sign",HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest((s+secret).getBytes(StandardCharsets.UTF_8))));return h;
    }
    static String required(Properties p,String k){String v=p.getProperty(k);if(v==null||v.isBlank())throw new IllegalArgumentException();return v;}
    static void validate(Map<String,String> b,String action) {
        var expected=new HashSet<>(List.of("operation","product","payeeId"));
        if(!action.equals("query"))expected.addAll(List.of("language","country","phone","birthdate","email","firstName","lastName","zip","city","state","address"));
        if(action.equals("update"))expected.addAll(List.of("requestId","version"));
        if(!b.keySet().equals(expected)||b.values().stream().anyMatch(String::isBlank)||!b.get("operation").equals(action.toUpperCase(Locale.ROOT))||!b.get("payeeId").matches("[A-Za-z0-9_-]{3,60}"))throw new IllegalArgumentException();
        if(!action.equals("query")) {
            if(!b.get("country").equals("US")||!b.get("birthdate").matches("[0-9]{4}-[0-9]{2}-[0-9]{2}"))throw new IllegalArgumentException();
            var born=java.time.LocalDate.parse(b.get("birthdate"));
            if(born.isBefore(java.time.LocalDate.of(1900,1,1))||born.isAfter(java.time.LocalDate.now(java.time.ZoneOffset.UTC)))throw new IllegalArgumentException();
            if(!b.get("language").matches("[a-zA-Z]{2}"))throw new IllegalArgumentException();
            if(!b.get("phone").matches("(\\+[0-9]{1,3}-)?[0-9]{4,12}"))throw new IllegalArgumentException();
            if(!b.get("email").matches("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9]+)"))throw new IllegalArgumentException();
            if(!b.get("firstName").matches("[A-Za-z\u00c0-\u00ff\u00df'-][A-Za-z\u00c0-\u00ff\u00df '-]{1,48}[A-Za-z\u00c0-\u00ff\u00df'-]"))throw new IllegalArgumentException();
            if(!b.get("lastName").matches("[A-Za-z\u00c0-\u00ff\u00df'-][A-Za-z\u00c0-\u00ff\u00df '-]{1,48}[A-Za-z\u00c0-\u00ff\u00df'-]"))throw new IllegalArgumentException();
            if(!b.get("zip").matches("[0-9]{5}(?:-[0-9]{4})?|[0-9]{5,10}"))throw new IllegalArgumentException();
            if(!b.get("city").matches("[A-Za-z\u00c0-\u00ff\u00df'&()\\-][A-Za-z\u00c0-\u00ff\u00df '&()\\-]{0,48}[A-Za-z\u00c0-\u00ff\u00df'&()\\-]"))throw new IllegalArgumentException();
            if(!b.get("state").matches("[A-Za-z\u00c0-\u00ff\u00df'&()\\-][A-Za-z\u00c0-\u00ff\u00df '&()\\-]{0,48}[A-Za-z\u00c0-\u00ff\u00df'&()\\-]"))throw new IllegalArgumentException();
            if(!b.get("address").matches("[0-9A-Za-z\u00c0-\u00ff\u00df #'(),\\-./:;\u00b0]{5,100}"))throw new IllegalArgumentException();
            if(b.get("email").length()>120)throw new IllegalArgumentException();
        }
        if(action.equals("update")&&(!b.get("requestId").matches("[A-Za-z0-9_-]{3,60}")||!b.get("version").matches("0|[1-9][0-9]*")))throw new IllegalArgumentException();
        if(b.get("product").length()>20)throw new IllegalArgumentException();
        if(action.equals("update")&&Long.parseLong(b.get("version"))==Long.MAX_VALUE)throw new IllegalArgumentException();
    }
    public static void main(String[] args) {
        try {
            if(args.length<2||args.length>3||(args.length==3&&!args[2].equals("--send")))throw new IllegalArgumentException();
            String action=args[0];if(!Set.of("create","query","update").contains(action))throw new IllegalArgumentException();
            Path file=Path.of(args[1]);if(Files.isSymbolicLink(file)||!Files.isRegularFile(file))throw new IllegalArgumentException();
            var perms=Files.getPosixFilePermissions(file);
            if(perms.stream().anyMatch(v->v.name().startsWith("GROUP_")||v.name().startsWith("OTHERS_")))throw new IllegalArgumentException();
            Properties p=new Properties();try(var r=Files.newBufferedReader(file,StandardCharsets.UTF_8)){p.load(r);}
            Map<String,String> b=new LinkedHashMap<>();
            for(String k:p.stringPropertyNames())b.put(k,p.getProperty(k));
            if(b.keySet().stream().anyMatch(Set.of("appId","nonce","timestamp","sign")::contains))throw new IllegalArgumentException();
            String gateway=System.getenv().getOrDefault("HISSPAY_GATEWAY","https://sandbox-api.hisspay.com");
            if(!Set.of("https://sandbox-api.hisspay.com","https://transaction.hisspay.com").contains(gateway))throw new IllegalArgumentException();
            validate(b,action);
            if(args.length==2){System.out.println("PREVIEW: "+gateway+"/transaction/payout/v1/beneficiaries/"+action+" fields="+b.keySet()+" (recipient data omitted)");return;}
            String app=Objects.requireNonNull(System.getenv("HISSPAY_APP_ID")),secret=Objects.requireNonNull(System.getenv("HISSPAY_APP_SECRET"));
            var h=sign(b,app,secret,Long.toString(System.currentTimeMillis()),UUID.randomUUID().toString());
            var req=HttpRequest.newBuilder(URI.create(gateway+"/transaction/payout/v1/beneficiaries/"+action)).timeout(Duration.ofSeconds(30)).header("Content-Type","application/json").POST(HttpRequest.BodyPublishers.ofString(json(b),StandardCharsets.UTF_8));h.forEach(req::header);
            var c=HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).followRedirects(HttpClient.Redirect.NEVER).build();
            var r=c.send(req.build(),HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));System.out.println("HTTP "+r.statusCode());System.out.println(r.body());
        }catch(Exception e){System.err.println("NOT_CONFIRMED: verify private configuration; query the same payeeId; never create a replacement recipient or automatically retry an update.");System.exit(1);}
    }
}
