package com.android.commands.monkey.ape.jev;
import java.io.*;import java.nio.file.*;import java.lang.reflect.*;import java.util.*;
import org.json.*;
import com.android.commands.monkey.ape.model.*;
import com.android.commands.monkey.ape.tree.*;
import com.android.commands.monkey.ape.utils.Config;
import com.android.commands.monkey.MonkeyKeyEvent;
import com.android.commands.monkey.MonkeyEvent;

/** No Android device, provider server, semantic review or network request. */
public final class JevFlowShadowTest {
    static int checks;
    static void check(boolean v,String m){checks++;if(!v)throw new AssertionError(m);}
    static List<JSONObject> rows(File p)throws Exception{List<JSONObject> out=new ArrayList<JSONObject>();for(String s:Files.readAllLines(p.toPath()))out.add(new JSONObject(s));return out;}
    static List<String> run(File directory,boolean enabled)throws Exception {
        directory.mkdirs();Config.setBoolean("ape.jev.flow_shadow",enabled);Config.setBoolean("ape.jev.probe.enabled",false);Config.setBoolean("ape.jev.regionBlocking.enabled",false);
        SemanticSyncBarrierTest.S state=SemanticSyncBarrierTest.state("s1");
        ModelAction action=new ModelAction(state,ActionType.MODEL_BACK);SemanticSyncBarrierTest.set(action,GraphElement.class,"id","a1");state.actions.add(action);action.setEnabled(true);action.setValid(true);
        // BACK requires no Name/Node; a hand-built observed tree is sufficient.
        GUITree tree=SemanticSyncBarrierTest.alloc(GUITree.class);tree.setCurrentState(state);tree.setTimestamp(1);SemanticSyncBarrierTest.set(tree,GUITree.class,"rootNode",SemanticSyncBarrierTest.node("root"));state.trees.add(tree);
        action.resolveAt(1,0,tree,null,null);
        SemanticSyncBarrierTest.G graph=new SemanticSyncBarrierTest.G();graph.disableGraphEvents();graph.setVerbose(false);graph.states.add(state);SemanticSyncBarrierTest.M model=new SemanticSyncBarrierTest.M(graph);
        JevRuntime runtime=new JevRuntime(directory,"http://[EXPERIMENT_HOST]:1/unused","",1000,32768,"unused");runtime.sync(model,1);
        List<String> trace=new ArrayList<String>();Random rng=new Random(47);int visits=action.getVisitedCount();boolean sat=action.isSaturated();
        runtime.shadowSelection(action,tree,1,"fixture");runtime.beforeInput(action);
        GUITreeAction concrete=action.getResolvedGUITreeAction();MonkeyKeyEvent key=new MonkeyKeyEvent(0,4);
        runtime.eventsGenerated(action,Collections.singletonList(key),0);runtime.eventInjected(key,MonkeyEvent.INJECT_SUCCESS);
        trace.add(action.getGraphId()+":"+action.getType()+":"+action.getVisitedCount()+":"+action.isSaturated()+":"+rng.nextLong());
        graph.addTransition(state,action,state,tree,concrete,tree);runtime.sync(model,2);runtime.sync(model,2);
        check(action.getVisitedCount()==visits && action.isSaturated()==sat,"journal leaves native action visits and saturation unchanged");
        runtime.beforeInput(action);MonkeyKeyEvent fail=new MonkeyKeyEvent(0,4);runtime.eventsGenerated(action,Collections.singletonList(fail),0);runtime.eventInjected(fail,MonkeyEvent.INJECT_FAIL);
        trace.add(action.getGraphId()+":"+action.getType()+":"+action.getVisitedCount()+":"+action.isSaturated()+":"+rng.nextLong());
        graph.addTransition(state,action,state,tree,concrete,tree);runtime.sync(model,3);
        // Reuse the same GUITreeAction object with a distinct successful input occurrence.
        runtime.beforeInput(action);MonkeyKeyEvent again=new MonkeyKeyEvent(0,4);runtime.eventsGenerated(action,Collections.singletonList(again),0);runtime.eventInjected(again,MonkeyEvent.INJECT_SUCCESS);
        graph.addTransition(state,action,state,tree,concrete,tree);runtime.sync(model,4);
        runtime.close();
        if(enabled){
            List<JSONObject> rs=rows(new File(directory,"region-flow-events.jsonl"));int confirmed=0;String input=null;boolean complete=false,failed=false;long seq=0;
            for(JSONObject r:rs){check(r.getLong("event_seq")==++seq,"strict journal order");
                if(r.getString("event_kind").equals("INPUT_COMPLETED")){complete|=r.optBoolean("execution_confirmed");failed|=!r.optBoolean("execution_confirmed");}
                if(r.getString("event_kind").equals("TRANSITION")&&r.optBoolean("execution_confirmed")){confirmed++;input=r.optString("input_id");check(r.getBoolean("is_back"),"Back retained");check(r.getString("source_state").equals("s1"),"actual source");}
            }
            check(confirmed==2,"two successful input occurrences, same action object; failed input does not inherit old confirmation");check(input!=null&&!input.isEmpty(),"input and transition linked");check(complete&&failed,"success and failed input separated");
            JSONObject status=new JSONObject(new String(Files.readAllBytes(new File(directory,"region-flow-status.json").toPath()),"UTF-8"));check(status.getInt("dropped")==0&&!status.getBoolean("failed"),"writer drained");
            JevFlowShadow full=new JevFlowShadow(new File(directory,"full-hook-benchmark.jsonl"),"synthetic-full-hooks",32768);
            long[] samples=new long[2000];GUITreeTransition tt=graph.getTreeHistory().get(graph.getTreeHistory().size()-1);
            for(int i=0;i<samples.length;i++){long t=System.nanoTime();full.beforeInput(action,i);full.inputCompleted(concrete,true,1,1,0,0);full.transition(tt,"synthetic-"+i,true,i==0,false,false);samples[i]=System.nanoTime()-t;}
            full.close();Arrays.sort(samples);long totalNs=0;for(long n:samples)totalNs+=n;
            int live=0;for(JSONObject row:rows(new File(directory,"full-hook-benchmark.jsonl")))if(row.getString("event_kind").equals("TRANSITION")&&row.optBoolean("execution_confirmed"))live++;
            check(live==2000,"new input during model-rebinding pass is not lost, input occurrences count separately");
            System.out.println("NATIVE_SYNTHETIC_FULL_HOOKS samples=2000 mean_ns="+(totalNs/2000)+" p50_ns="+samples[1000]+" p95_ns="+samples[1900]+" max_ns="+samples[1999]);

            JevFlowShadow cut=new JevFlowShadow(new File(directory,"boundaries.jsonl"),"boundaries",32);
            cut.beforeInput(action,1);cut.inputCompleted(concrete,true,1,1,0,0);
            cut.beforeInput(new Action(ActionType.EVENT_RESTART),2);cut.transition(tt,"after-restart",true,false,false,false);
            cut.beforeInput(action,3);cut.inputCompleted(concrete,true,1,1,0,0);
            cut.boundary("interrupted");cut.transition(tt,"after-boundary",true,false,false,false);cut.close();
            int boundaryLive=0,unconfirmed=0;for(JSONObject row:rows(new File(directory,"boundaries.jsonl")))
                if(row.getString("event_kind").equals("TRANSITION")){if(row.optBoolean("execution_confirmed"))boundaryLive++;else unconfirmed++;}
            check(boundaryLive==0,"restart/trace boundary cannot reuse a pending pre-boundary input");
            check(unconfirmed==2,"post-boundary transitions remain diagnostic, not GUI flow");

        } else check(!new File(directory,"region-flow-events.jsonl").exists(),"disabled creates no flow journal");
        return trace;
    }
    public static void main(String[] args)throws Exception {
        File root=new File(args[0]);root.mkdirs();List<String> off=run(new File(root,"off"),false),on=run(new File(root,"on"),true);check(off.equals(on),"fixed input/action/RNG trace unchanged with flow enabled");
        Config.setBoolean("ape.jev.flow_shadow",true);check(JevFlowShadow.open(new File(root,"does-not-exist/child"),"failure")==null,"open failure is fail-open");
        JevFlowShadow shadow=new JevFlowShadow(new File(root,"fault.jsonl"),"fault",1);Field f=JevFlowShadow.class.getDeclaredField("failed");f.setAccessible(true);f.setBoolean(shadow,true);shadow.boundary("injected_failure");shadow.close();check(true,"writer failure does not throw into APE");
        JevFlowShadow bench=new JevFlowShadow(new File(root,"benchmark.jsonl"),"benchmark",32768);long start=System.nanoTime();for(int i=0;i<10000;i++)bench.boundary("synthetic_boundary");long ns=System.nanoTime()-start;bench.close();
        System.out.println("NATIVE_SYNTHETIC_ENQUEUE events=10000 elapsed_ns="+ns+" ns_per_event="+(ns/10000));
        for(String t:on)System.out.println("FLOW_DECISION_TRACE "+t);
        System.out.println("PASS JevFlowShadow local checks="+checks+" no devices/providers");
    }
}
