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+, single request, preview by default. Never automatically retry an apply. */
public class PayoutRequest {
    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)+":"+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 boolean codesOptional(Map<String,String> b) {
        return "CARD".equals(b.get("payCode")) && (("USD".equals(b.get("currency")) && Set.of("USD-PAYPLUS-PAYOUT","USD-LOCAL-PAYOUT").contains(b.getOrDefault("product",""))) || ("AUD".equals(b.get("currency")) && "AUD-LOCAL-PAYOUT".equals(b.get("product"))));
    }
    static void validateTarget(Map<String,String> body,String gateway) {
        if(body.getOrDefault("account", "").startsWith("SANDBOX-") && !gateway.equals("https://sandbox-api.hisspay.com"))throw new IllegalArgumentException("Synthetic payout requires sandbox");
    }
    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("apply","query","balance").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<>();
            List<String> fields=switch(action){case "balance"->List.of("currency");case "query"->List.of("merchantOrderNo");default->List.of("merchantOrderNo","amount","currency","product","accountName","account","phone","email","payCode","notifyUrl");};
            for(String k:fields)b.put(k,required(p,k));
            if(action.equals("apply"))for(String k:List.of("bizCode","branchCode")) {
                if(!codesOptional(b))b.put(k,required(p,k));
                else if(p.containsKey(k))b.put(k,p.getProperty(k));
            }
            if(action.equals("apply"))for(String k:List.of("extra","address","accountId","bankName","swiftCode"))if(p.containsKey(k))b.put(k,p.getProperty(k));
            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();
            validateTarget(b,gateway);
            if(args.length==2){System.out.println("PREVIEW: "+gateway+"/transaction/payout/v1/"+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/"+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 original merchantOrderNo before any new payout.");System.exit(1);}
    }
}
