import { Form, Link, redirect, useActionData, useNavigation } from "react-router";
import type { Route } from "./+types/admin.sports-seasons.$id.simulator";

import { AlertCircle, ArrowLeft, CheckCircle2, Loader2, Play, Save, SlidersHorizontal } from "lucide-react";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "~/components/ui/card";
import { Textarea } from "~/components/ui/textarea";
import { findParticipantsBySportsSeasonId } from "~/models/season-participant";
import { findSportsSeasonById } from "~/models/sports-season";
import {
  batchUpsertParticipantSimulatorInputs,
  getParticipantSimulatorInputs,
  getSportsSeasonSimulatorConfig,
  upsertSportsSeasonSimulatorConfig,
  validateSimulatorReadiness,
  type UpsertParticipantSimulatorInput,
} from "~/models/simulator";
import { normalizeName } from "~/lib/fuzzy-match";
import { getSimulatorInputPolicy, type MissingEloStrategy, type MissingRatingStrategy } from "~/services/simulations/input-policy";
import { runSportsSeasonSimulation } from "~/services/simulations/runner";

export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors {
  return [{ title: `Simulator Setup - ${data?.sportsSeason?.name ?? "Sports Season"} - Brackt Admin` }];
}

export async function loader({ params }: Route.LoaderArgs) {
  const sportsSeason = await findSportsSeasonById(params.id);
  if (!sportsSeason) {
    throw new Response("Sports season not found", { status: 404 });
  }

  const [participants, config, inputs, readiness] = await Promise.all([
    findParticipantsBySportsSeasonId(params.id),
    getSportsSeasonSimulatorConfig(params.id),
    getParticipantSimulatorInputs(params.id),
    validateSimulatorReadiness(params.id),
  ]);

  if (!config) {
    throw new Response("This sports season does not have a simulator configured.", { status: 404 });
  }

  const inputMap = new Map(inputs.map((input) => [input.participantId, input]));
  const inputRows = participants.map((participant) => ({
    participant,
    input: inputMap.get(participant.id) ?? null,
  }));

  const inputPolicy = getSimulatorInputPolicy(config.config);

  return { sportsSeason, participants, config, inputRows, readiness, inputPolicy };
}

interface ActionData {
  success?: boolean;
  message: string;
}

function parseOptionalNumber(value: string | undefined): number | null {
  if (value === undefined || value.trim() === "") return null;
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : null;
}

function parsePolicyNumber(formData: FormData, key: string, fallback: number): number {
  const value = formData.get(key);
  if (typeof value !== "string" || value.trim() === "") return fallback;
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : fallback;
}

function parseMissingEloStrategy(value: FormDataEntryValue | null): MissingEloStrategy {
  return value === "fallbackElo" || value === "averageKnown" || value === "worstKnownMinus"
    ? value
    : "block";
}

function parseMissingRatingStrategy(value: FormDataEntryValue | null): MissingRatingStrategy {
  return value === "fallbackRating" || value === "averageKnown" || value === "worstKnownMinus"
    ? value
    : "block";
}

function findParticipantId(name: string, participants: Array<{ id: string; name: string }>): string | null {
  const normalizedInput = normalizeName(name);
  const normalized = participants.map((participant) => ({
    participant,
    normalized: normalizeName(participant.name),
  }));

  return (
    normalized.find((candidate) => candidate.normalized === normalizedInput)?.participant.id ??
    normalized.find((candidate) =>
      candidate.normalized.includes(normalizedInput) || normalizedInput.includes(candidate.normalized)
    )?.participant.id ??
    null
  );
}

function parseInputCsv(
  text: string,
  sportsSeasonId: string,
  participants: Array<{ id: string; name: string }>
): { inputs: UpsertParticipantSimulatorInput[]; unmatched: string[] } {
  const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
  if (lines.length === 0) return { inputs: [], unmatched: [] };

  const header = lines[0].split(",").map((value) => value.trim());
  const indexes = new Map(header.map((value, index) => [value, index]));
  const nameIndex = indexes.get("name");
  if (nameIndex === undefined) {
    throw new Error("Bulk input CSV must include a `name` column.");
  }

  const inputs: UpsertParticipantSimulatorInput[] = [];
  const unmatched: string[] = [];

  for (const line of lines.slice(1)) {
    const cols = line.split(",").map((value) => value.trim());
    const name = cols[nameIndex];
    if (!name) continue;

    const participantId = findParticipantId(name, participants);
    if (!participantId) {
      unmatched.push(name);
      continue;
    }

    inputs.push({
      participantId,
      sportsSeasonId,
      sourceElo: parseOptionalNumber(cols[indexes.get("sourceElo") ?? -1]) ?? undefined,
      sourceOdds: parseOptionalNumber(cols[indexes.get("sourceOdds") ?? -1]) ?? undefined,
      worldRanking: parseOptionalNumber(cols[indexes.get("worldRanking") ?? -1]) ?? undefined,
      rating: parseOptionalNumber(cols[indexes.get("rating") ?? -1]) ?? undefined,
      projectedWins: parseOptionalNumber(cols[indexes.get("projectedWins") ?? -1]) ?? undefined,
      projectedTablePoints: parseOptionalNumber(cols[indexes.get("projectedTablePoints") ?? -1]) ?? undefined,
      seed: parseOptionalNumber(cols[indexes.get("seed") ?? -1]) ?? undefined,
      region: cols[indexes.get("region") ?? -1] || undefined,
    });
  }

  return { inputs, unmatched };
}

export async function action({ request, params }: Route.ActionArgs): Promise<ActionData | Response> {
  const formData = await request.formData();
  const intent = formData.get("intent");
  const sportsSeasonId = params.id;

  if (intent === "run") {
    try {
      await runSportsSeasonSimulation(sportsSeasonId);
      return redirect(`/admin/sports-seasons/${sportsSeasonId}/expected-values`);
    } catch (error) {
      return { success: false, message: error instanceof Error ? error.message : "Simulation failed." };
    }
  }

  if (intent === "save-config") {
    const rawConfig = formData.get("config");
    const currentConfig = await getSportsSeasonSimulatorConfig(sportsSeasonId);
    if (!currentConfig) return { success: false, message: "Simulator config not found." };

    try {
      const parsed = typeof rawConfig === "string" && rawConfig.trim()
        ? JSON.parse(rawConfig) as Record<string, unknown>
        : {};
      // Preserve an existing inputPolicy if the submitted JSON omits it, so
      // editing other config keys doesn't silently wipe a previously configured
      // missing-Elo strategy.
      const config = "inputPolicy" in parsed
        ? parsed
        : { ...parsed, ...(currentConfig.config.inputPolicy !== undefined ? { inputPolicy: currentConfig.config.inputPolicy } : {}) };
      await upsertSportsSeasonSimulatorConfig({
        sportsSeasonId,
        simulatorType: currentConfig.simulatorType,
        config,
      });
      return { success: true, message: "Simulator config saved." };
    } catch (error) {
      return { success: false, message: error instanceof Error ? error.message : "Invalid config JSON." };
    }
  }

  if (intent === "save-input-policy") {
    const currentConfig = await getSportsSeasonSimulatorConfig(sportsSeasonId);
    if (!currentConfig) return { success: false, message: "Simulator config not found." };

    const currentPolicy = getSimulatorInputPolicy(currentConfig.config);
    await upsertSportsSeasonSimulatorConfig({
      sportsSeasonId,
      simulatorType: currentConfig.simulatorType,
      config: {
        ...currentConfig.config,
        inputPolicy: {
          missingEloStrategy: parseMissingEloStrategy(formData.get("missingEloStrategy")),
          missingRatingStrategy: parseMissingRatingStrategy(formData.get("missingRatingStrategy")),
          fallbackElo: parsePolicyNumber(formData, "fallbackElo", currentPolicy.fallbackElo),
          fallbackEloDelta: parsePolicyNumber(formData, "fallbackEloDelta", currentPolicy.fallbackEloDelta),
          eloMin: parsePolicyNumber(formData, "eloMin", currentPolicy.eloMin),
          eloMax: parsePolicyNumber(formData, "eloMax", currentPolicy.eloMax),
          fallbackRating: parsePolicyNumber(formData, "fallbackRating", currentPolicy.fallbackRating),
          fallbackRatingDelta: parsePolicyNumber(formData, "fallbackRatingDelta", currentPolicy.fallbackRatingDelta),
          ratingMin: parsePolicyNumber(formData, "ratingMin", currentPolicy.ratingMin),
          ratingMax: parsePolicyNumber(formData, "ratingMax", currentPolicy.ratingMax),
        },
      },
    });
    return { success: true, message: "Simulator input policy saved." };
  }

  if (intent === "save-inputs") {
    const rawInputs = formData.get("bulkInputs");
    if (typeof rawInputs !== "string" || rawInputs.trim() === "") {
      return { success: false, message: "Paste at least one input row before saving." };
    }

    const participants = await findParticipantsBySportsSeasonId(sportsSeasonId);
    try {
      const parsed = parseInputCsv(rawInputs, sportsSeasonId, participants);
      if (parsed.inputs.length === 0) {
        return { success: false, message: "No matching participants found in the pasted inputs." };
      }
      await batchUpsertParticipantSimulatorInputs(parsed.inputs);
      const suffix = parsed.unmatched.length > 0
        ? ` ${parsed.unmatched.length} row(s) were unmatched: ${parsed.unmatched.join(", ")}.`
        : "";
      return { success: true, message: `Saved ${parsed.inputs.length} simulator input row(s).${suffix}` };
    } catch (error) {
      return { success: false, message: error instanceof Error ? error.message : "Failed to save simulator inputs." };
    }
  }

  return { success: false, message: "Unknown simulator setup action." };
}

export default function AdminSportsSeasonSimulator({ loaderData }: Route.ComponentProps) {
  const { sportsSeason, config, inputRows, readiness, inputPolicy } = loaderData;
  const actionData = useActionData<ActionData>();
  const navigation = useNavigation();
  const isSubmitting = navigation.state === "submitting";
  const setupSections = config.profile.setupSections;
  const sourceEloAlternatives = config.profile.derivableInputs?.sourceElo ?? [];
  const ratingAlternatives = config.profile.derivableInputs?.rating ?? [];
  const showsInputPolicy =
    config.profile.requiredInputs.includes("sourceElo") || config.profile.requiredInputs.includes("rating");

  return (
    <div className="container mx-auto p-6 space-y-6">
      <div className="flex items-center gap-4">
        <Button variant="ghost" size="sm" asChild>
          <Link to={`/admin/sports-seasons/${sportsSeason.id}`}>
            <ArrowLeft className="h-4 w-4 mr-2" />
            Back to Sports Season
          </Link>
        </Button>
        <Button variant="ghost" size="sm" asChild>
          <Link to="/admin/simulators">All Simulators</Link>
        </Button>
      </div>

      <div>
        <h1 className="text-3xl font-bold flex items-center gap-2">
          <SlidersHorizontal className="h-7 w-7" />
          Simulator Setup
        </h1>
        <p className="text-muted-foreground mt-1">
          {sportsSeason.sport.name} - {sportsSeason.name}
        </p>
      </div>

      {actionData && (
        <Card className={actionData.success ? "border-emerald-500/40" : "border-destructive/40"}>
          <CardContent className="py-4 text-sm">{actionData.message}</CardContent>
        </Card>
      )}

      <Card>
        <CardHeader>
          <div className="flex items-center justify-between gap-4">
            <div>
              <CardTitle>{config.profile.displayName}</CardTitle>
              <CardDescription>{config.profile.description}</CardDescription>
            </div>
            {readiness.status === "ready" ? (
              <Badge className="gap-1"><CheckCircle2 className="h-3 w-3" /> Ready</Badge>
            ) : (
              <Badge variant="secondary" className="gap-1"><AlertCircle className="h-3 w-3" /> Needs setup</Badge>
            )}
          </div>
        </CardHeader>
        <CardContent className="space-y-4">
          <div className="grid gap-3 md:grid-cols-3">
            <div className="rounded-md border p-3">
              <div className="text-sm text-muted-foreground">Simulator Type</div>
              <code className="text-sm">{config.simulatorType}</code>
            </div>
            <div className="rounded-md border p-3">
              <div className="text-sm text-muted-foreground">Participant Inputs</div>
              <div className="font-medium">{readiness.participantInputCount}/{readiness.participantCount}</div>
            </div>
            <div className="rounded-md border p-3">
              <div className="text-sm text-muted-foreground">Simulation Status</div>
              <div className="font-medium">{sportsSeason.simulationStatus}</div>
            </div>
          </div>

          {readiness.missingInputs.length > 0 && (
            <div className="rounded-md border border-amber-500/30 bg-amber-500/10 p-3 text-sm">
              Missing: {readiness.missingInputs.join(", ")}
            </div>
          )}

          {readiness.warnings.length > 0 && (
            <div className="rounded-md border border-sky-500/30 bg-sky-500/10 p-3 text-sm">
              {readiness.warnings.join(" ")}
            </div>
          )}

          <div className="flex flex-wrap gap-2">
            {setupSections.includes("futuresOdds") && <Button variant="outline" size="sm" asChild><Link to={`/admin/sports-seasons/${sportsSeason.id}/futures-odds`}>Futures Odds</Link></Button>}
            {setupSections.includes("eloRatings") && <Button variant="outline" size="sm" asChild><Link to={`/admin/sports-seasons/${sportsSeason.id}/elo-ratings`}>Elo Ratings</Link></Button>}
            {setupSections.includes("surfaceElo") && <Button variant="outline" size="sm" asChild><Link to={`/admin/sports-seasons/${sportsSeason.id}/surface-elo`}>Surface Elo</Link></Button>}
            {setupSections.includes("golfSkills") && <Button variant="outline" size="sm" asChild><Link to={`/admin/sports-seasons/${sportsSeason.id}/golf-skills`}>Golf Skills</Link></Button>}
            {setupSections.includes("regularStandings") && <Button variant="outline" size="sm" asChild><Link to={`/admin/sports-seasons/${sportsSeason.id}/regular-standings`}>Regular Standings</Link></Button>}
            {setupSections.includes("events") && <Button variant="outline" size="sm" asChild><Link to={`/admin/sports-seasons/${sportsSeason.id}/events`}>Events</Link></Button>}
            <Button variant="outline" size="sm" asChild><Link to={`/admin/sports-seasons/${sportsSeason.id}/expected-values`}>Expected Values</Link></Button>
          </div>

          <Form method="post">
            <input type="hidden" name="intent" value="run" />
            <Button type="submit" disabled={!readiness.canRun || sportsSeason.simulationStatus === "running" || isSubmitting}>
              {isSubmitting ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Play className="mr-2 h-4 w-4" />}
              Run Simulation
            </Button>
          </Form>
        </CardContent>
      </Card>

      {showsInputPolicy && (
        <Card>
          <CardHeader>
            <CardTitle>Input Policy</CardTitle>
            <CardDescription>
              Direct inputs win. This simulator can derive Elo from{" "}
              {sourceEloAlternatives.length > 0 ? sourceEloAlternatives.join(", ") : "no alternate Elo inputs"}
              {ratingAlternatives.length > 0 ? ` and ratings from ${ratingAlternatives.join(", ")}` : ""}.
              Tail fallbacks are explicit so low-impact missing participants do not silently get invented ratings.
            </CardDescription>
          </CardHeader>
          <CardContent>
            <Form method="post" className="grid gap-4 md:grid-cols-5">
              <input type="hidden" name="intent" value="save-input-policy" />
              {config.profile.requiredInputs.includes("sourceElo") && (
                <>
                  <div className="space-y-2 md:col-span-2">
                    <Label htmlFor="missingEloStrategy">Missing Elo Strategy</Label>
                    <select
                      id="missingEloStrategy"
                      name="missingEloStrategy"
                      className="h-9 w-full rounded-md border bg-background px-3 text-sm"
                      defaultValue={inputPolicy.missingEloStrategy}
                    >
                      <option value="block">Block until complete</option>
                      <option value="fallbackElo">Use fixed fallback Elo</option>
                      <option value="averageKnown">Use average known Elo</option>
                      <option value="worstKnownMinus">Use worst known Elo minus delta</option>
                    </select>
                  </div>
                  <div className="space-y-2">
                    <Label htmlFor="fallbackElo">Fallback Elo</Label>
                    <Input id="fallbackElo" name="fallbackElo" type="number" defaultValue={inputPolicy.fallbackElo} />
                  </div>
                  <div className="space-y-2">
                    <Label htmlFor="fallbackEloDelta">Worst Elo Delta</Label>
                    <Input id="fallbackEloDelta" name="fallbackEloDelta" type="number" defaultValue={inputPolicy.fallbackEloDelta} />
                  </div>
                  <div className="space-y-2">
                    <Label htmlFor="eloMin">Elo Floor</Label>
                    <Input id="eloMin" name="eloMin" type="number" defaultValue={inputPolicy.eloMin} />
                  </div>
                  <div className="space-y-2">
                    <Label htmlFor="eloMax">Elo Ceiling</Label>
                    <Input id="eloMax" name="eloMax" type="number" defaultValue={inputPolicy.eloMax} />
                  </div>
                </>
              )}
              {config.profile.requiredInputs.includes("rating") && (
                <>
                  <div className="space-y-2 md:col-span-2">
                    <Label htmlFor="missingRatingStrategy">Missing Rating Strategy</Label>
                    <select
                      id="missingRatingStrategy"
                      name="missingRatingStrategy"
                      className="h-9 w-full rounded-md border bg-background px-3 text-sm"
                      defaultValue={inputPolicy.missingRatingStrategy}
                    >
                      <option value="block">Block until complete</option>
                      <option value="fallbackRating">Use fixed fallback rating</option>
                      <option value="averageKnown">Use average known rating</option>
                      <option value="worstKnownMinus">Use worst known rating minus delta</option>
                    </select>
                  </div>
                  <div className="space-y-2">
                    <Label htmlFor="fallbackRating">Fallback Rating</Label>
                    <Input id="fallbackRating" name="fallbackRating" type="number" step="0.01" defaultValue={inputPolicy.fallbackRating} />
                  </div>
                  <div className="space-y-2">
                    <Label htmlFor="fallbackRatingDelta">Worst Rating Delta</Label>
                    <Input id="fallbackRatingDelta" name="fallbackRatingDelta" type="number" step="0.01" defaultValue={inputPolicy.fallbackRatingDelta} />
                  </div>
                </>
              )}
              <div className="space-y-2">
                <Label htmlFor="ratingMin">Rating Floor</Label>
                <Input id="ratingMin" name="ratingMin" type="number" step="0.01" defaultValue={inputPolicy.ratingMin} />
              </div>
              <div className="space-y-2">
                <Label htmlFor="ratingMax">Rating Ceiling</Label>
                <Input id="ratingMax" name="ratingMax" type="number" step="0.01" defaultValue={inputPolicy.ratingMax} />
              </div>
              <div className="md:col-span-5">
                <Button type="submit" disabled={isSubmitting}>
                  <Save className="mr-2 h-4 w-4" />
                  Save Input Policy
                </Button>
              </div>
            </Form>
          </CardContent>
        </Card>
      )}

      <Card>
        <CardHeader>
          <CardTitle>Season Config</CardTitle>
          <CardDescription>
            JSON overrides for this specific sports season. Defaults come from the simulator profile.
          </CardDescription>
        </CardHeader>
        <CardContent>
          <Form method="post" className="space-y-4">
            <input type="hidden" name="intent" value="save-config" />
            <Textarea
              name="config"
              rows={10}
              className="font-mono text-sm"
              defaultValue={JSON.stringify(config.config, null, 2)}
            />
            <Button type="submit" disabled={isSubmitting}>
              <Save className="mr-2 h-4 w-4" />
              Save Config
            </Button>
          </Form>
        </CardContent>
      </Card>

      <Card>
        <CardHeader>
          <CardTitle>Bulk Simulator Inputs</CardTitle>
          <CardDescription>
            Paste CSV with a header row. Supported columns: name, sourceElo, sourceOdds, worldRanking, rating,
            projectedWins, projectedTablePoints, seed, region. Values must not contain commas.
          </CardDescription>
        </CardHeader>
        <CardContent className="space-y-4">
          <Form method="post" className="space-y-4">
            <input type="hidden" name="intent" value="save-inputs" />
            <Textarea
              name="bulkInputs"
              rows={8}
              className="font-mono text-sm"
              placeholder={`name,sourceElo,sourceOdds,worldRanking,rating\n${inputRows[0]?.participant.name ?? "Team Name"},1500,+2500,12,28.5`}
            />
            <Button type="submit" disabled={isSubmitting}>
              <Save className="mr-2 h-4 w-4" />
              Save Inputs
            </Button>
          </Form>

          <div className="rounded-md border">
            <div className="grid grid-cols-6 gap-2 border-b px-3 py-2 text-xs font-medium text-muted-foreground">
              <div className="col-span-2">Participant</div>
              <div>Elo</div>
              <div>Odds</div>
              <div>Rank</div>
              <div>Rating</div>
            </div>
            {inputRows.slice(0, 20).map(({ participant, input }) => (
              <div key={participant.id} className="grid grid-cols-6 gap-2 border-b last:border-b-0 px-3 py-2 text-sm">
                <div className="col-span-2 font-medium">{participant.name}</div>
                <div>{input?.sourceElo ?? "—"}</div>
                <div>{input?.sourceOdds ?? "—"}</div>
                <div>{input?.worldRanking ?? "—"}</div>
                <div>{input?.rating ?? "—"}</div>
              </div>
            ))}
            {inputRows.length > 20 && (
              <div className="px-3 py-2 text-xs text-muted-foreground">
                Showing 20 of {inputRows.length} participants
              </div>
            )}
          </div>
        </CardContent>
      </Card>
    </div>
  );
}
