import {
  AgenticaSystemPrompt,
  IAgenticaController,
  MicroAgentica,
} from "@agentica/core";
import {
  AutoBeTest,
  AutoBeTestValidateEvent,
  IAutoBeTypeScriptCompileResult,
} from "@autobe/interface";
import {
  ILlmApplication,
  ILlmSchema,
  OpenApi,
  OpenApiTypeChecker,
} from "@samchon/openapi";
import { IPointer } from "tstl";
import typia, { IJsonSchemaUnit } from "typia";

import { AutoBeSystemPromptConstant } from "../../constants/AutoBeSystemPromptConstant";
import { AutoBeContext } from "../../context/AutoBeContext";
import { assertSchemaModel } from "../../context/assertSchemaModel";
import { enforceToolCall } from "../../utils/enforceToolCall";
import { IAutoBeTestWriteResult } from "./structures/IAutoBeTestWriteResult";
import { transformTestCorrectHistories } from "./transformTestCorrectHistories";

export function orchestrateTestCorrect<Model extends ILlmSchema.Model>(
  ctx: AutoBeContext<Model>,
  results: IAutoBeTestWriteResult[],
  life: number = 4,
): Promise<AutoBeTestValidateEvent[]> {
  return Promise.all(
    results.map(async (written) => {
      const event: AutoBeTestValidateEvent = await compile(ctx, written);
      return predicate(ctx, written, event, life);
    }),
  );
}

async function predicate<Model extends ILlmSchema.Model>(
  ctx: AutoBeContext<Model>,
  written: IAutoBeTestWriteResult,
  event: AutoBeTestValidateEvent,
  life: number,
): Promise<AutoBeTestValidateEvent> {
  ctx.dispatch(event);
  return event.result.type === "failure"
    ? correct(ctx, written, event, life - 1)
    : event;
}

async function correct<Model extends ILlmSchema.Model>(
  ctx: AutoBeContext<Model>,
  written: IAutoBeTestWriteResult,
  event: AutoBeTestValidateEvent,
  life: number,
): Promise<AutoBeTestValidateEvent> {
  if (event.result.type !== "failure") return event;
  else if (--life <= 0) return event;

  const pointer: IPointer<ICorrectTestFunctionProps | null> = {
    value: null,
  };
  const agentica = new MicroAgentica({
    model: ctx.model,
    vendor: { ...ctx.vendor },
    config: {
      ...(ctx.config ?? {}),
      executor: {
        describe: null,
      },
      systemPrompt: {
        validate: (events) =>
          [
            AgenticaSystemPrompt.VALIDATE_REPEATED.replace(
              "${{HISTORICAL_ERRORS}}",
              JSON.stringify(events.map((e) => e.result.errors)),
            ),
            AutoBeSystemPromptConstant.TEST_VALIDATE.replace(
              "${{AutoBeTest.IStatement}}",
              getUnionTypeName(typia.json.schema<AutoBeTest.IStatement>()),
            ).replace(
              "${{AutoBeTest.IExpression}}",
              getUnionTypeName(typia.json.schema<AutoBeTest.IExpression>()),
            ),
          ].join("\n\n"),
      },
      retry: 4,
      throw: true,
    },
    histories: transformTestCorrectHistories(written, event.result),
    controllers: [
      createController({
        model: ctx.model,
        build: (next) => {
          pointer.value = next;
        },
      }),
    ],
  });
  enforceToolCall(agentica);

  await agentica
    .conversate(
      "Fix the `AutoBeTest.IFunction` data to resolve the compilation error.",
    )
    .finally(() => {
      const tokenUsage = agentica.getTokenUsage();
      ctx.usage().record(tokenUsage, ["test"]);
    });
  if (pointer.value === null) throw new Error("Failed to modify test code.");
  event = await compile(ctx, {
    ...written,
    file: {
      ...written.file,
      function: pointer.value.function,
    },
  });
  return predicate(ctx, written, event, life);
}

async function compile<Model extends ILlmSchema.Model>(
  ctx: AutoBeContext<Model>,
  written: IAutoBeTestWriteResult,
): Promise<AutoBeTestValidateEvent> {
  const compiled: IAutoBeTypeScriptCompileResult =
    await ctx.compiler.test.compile({
      files: {
        ...written.artifacts.dto,
        ...written.artifacts.sdk,
        [written.file.location]: written.file.content,
      },
    });
  return {
    type: "testValidate",
    file: written.file,
    result: compiled,
    created_at: new Date().toISOString(),
    step: ctx.state().analyze?.step ?? 0,
  };
}

function getUnionTypeName(unit: IJsonSchemaUnit): string {
  if (OpenApiTypeChecker.isReference(unit.schema) === false) return "unknown";

  const typeName: string = unit.schema.$ref.split("/").pop() ?? "";
  const schema: OpenApi.IJsonSchema | undefined =
    unit.components.schemas?.[typeName];
  if (schema === undefined || OpenApiTypeChecker.isOneOf(schema) === false)
    return "unknown";
  return schema.oneOf
    .filter(OpenApiTypeChecker.isReference)
    .map((r) => r.$ref.split("/").pop() ?? "")
    .join(" | ");
}

function createController<Model extends ILlmSchema.Model>(props: {
  model: Model;
  build: (next: ICorrectTestFunctionProps) => void;
}): IAgenticaController.IClass<Model> {
  assertSchemaModel(props.model);

  const application: ILlmApplication<Model> = collection[
    props.model
  ] satisfies ILlmApplication<any> as unknown as ILlmApplication<Model>;
  return {
    protocol: "class",
    name: "Modify Test Code",
    application,
    execute: {
      correctTestCode: (next) => {
        props.build(next);
      },
    } satisfies IApplication,
  };
}

const claude = typia.llm.application<
  IApplication,
  "claude"
>();
const collection = {
  chatgpt: typia.llm.application<
    IApplication,
    "chatgpt"
  >(),
  claude,
  llama: claude,
  deepseek: claude,
  "3.1": claude,
};

interface IApplication {
  correctTestCode(props: ICorrectTestFunctionProps): void;
}

interface ICorrectTestFunctionProps {
  /**
   * Step 1: Initial self-reflection on the source code without compiler error
   * context.
   *
   * The AI agent analyzes the previously generated test code to identify
   * potential issues, relying solely on its understanding of TypeScript syntax,
   * testing patterns, and best practices.
   *
   * This encourages the agent to develop independent debugging skills before
   * being influenced by external error messages.
   */
  think_without_compile_error: string;

  /**
   * Step 2: Re-evaluation of the code with compiler error messages as
   * additional context.
   *
   * After the initial analysis, the AI agent reviews the same code again, this
   * time incorporating the specific TypeScript compiler error messages.
   *
   * This allows the agent to correlate its initial observations with concrete
   * compilation failures and refine its understanding of what went wrong.
   */
  think_again_with_compile_error: string;

  /**
   * Step 3: Concrete action plan for fixing the identified issues.
   *
   * Based on the analysis from steps 1 and 2, the AI agent formulates a
   * specific, step-by-step solution strategy.
   *
   * This should include what changes need to be made, why those changes are
   * necessary, and how they will resolve the compilation errors while
   * maintaining the test's intended functionality.
   */
  solution: string;

  /** Re-written AST data to fix the compilation error. */
  function: AutoBeTest.IFunction;
}
