import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { eq, and } from "drizzle-orm";
import { z } from "zod";

// ─── Validation schema ────────────────────────────────────────────────────────
// Used both for runtime validation of imported files and as the canonical shape
// for the exported JSON (fixes: zod validation, calculatedAt, participantResults)

const ImportDataSchema = z.object({
  version: z.string(),
  exportedAt: z.string(),
  sports: z.array(
    z.object({
      name: z.string(),
      type: z.string(),
      slug: z.string(),
      description: z.string().nullable(),
    })
  ),
  sportsSeasons: z.array(
    z.object({
      sportSlug: z.string(),
      name: z.string(),
      year: z.number(),
      startDate: z.string().nullable(),
      endDate: z.string().nullable(),
      status: z.string(),
      scoringType: z.string(),
    })
  ),
  participants: z.array(
    z.object({
      sportSlug: z.string(),
      seasonName: z.string(),
      seasonYear: z.number(),
      name: z.string(),
      shortName: z.string().nullable(),
      externalId: z.string().nullable(),
      expectedValue: z.union([z.number(), z.string()]),
    })
  ),
  // Optional for backward compatibility with v1.0 exports
  participantExpectedValues: z
    .array(
      z.object({
        sportSlug: z.string(),
        seasonName: z.string(),
        seasonYear: z.number(),
        participantName: z.string(),
        probFirst: z.string(),
        probSecond: z.string(),
        probThird: z.string(),
        probFourth: z.string(),
        probFifth: z.string(),
        probSixth: z.string(),
        probSeventh: z.string(),
        probEighth: z.string(),
        expectedValue: z.string(),
        source: z.string().nullable(),
        sourceOdds: z.number().nullable(),
        calculatedAt: z.string().optional(), // optional for backward compat
      })
    )
    .optional(),
  participantResults: z
    .array(
      z.object({
        sportSlug: z.string(),
        seasonName: z.string(),
        seasonYear: z.number(),
        participantName: z.string(),
        finalPosition: z.number().nullable(),
        qualifyingPoints: z.string().nullable(),
        notes: z.string().nullable(),
      })
    )
    .optional(),
  seasonTemplates: z.array(
    z.object({
      name: z.string(),
      year: z.number(),
      description: z.string().nullable(),
      isActive: z.boolean(),
      sports: z.array(
        z.object({
          sportSlug: z.string(),
          seasonName: z.string(),
          seasonYear: z.number(),
        })
      ),
    })
  ),
});

type ExportData = z.infer<typeof ImportDataSchema>;

// ─── Export ───────────────────────────────────────────────────────────────────

export async function exportSportsDataToJSON(): Promise<ExportData> {
  const db = database();

  const [
    sports,
    sportsSeasons,
    participants,
    participantEVs,
    participantResultsData,
    seasonTemplates,
  ] = await Promise.all([
    db.query.sports.findMany({
      orderBy: (s, { asc }) => [asc(s.name)],
    }),
    db.query.sportsSeasons.findMany({
      with: { sport: true },
      orderBy: (s, { desc, asc }) => [desc(s.year), asc(s.name)],
    }),
    db.query.participants.findMany({
      with: { sportsSeason: { with: { sport: true } } },
      orderBy: (p, { asc }) => [asc(p.name)],
    }),
    db.query.participantExpectedValues.findMany({
      with: {
        participant: { with: { sportsSeason: { with: { sport: true } } } },
      },
    }),
    db.query.participantResults.findMany({
      with: {
        participant: { with: { sportsSeason: { with: { sport: true } } } },
      },
    }),
    db.query.seasonTemplates.findMany({
      with: {
        seasonTemplateSports: {
          with: { sportsSeason: { with: { sport: true } } },
        },
      },
      orderBy: (t, { desc }) => [desc(t.year)],
    }),
  ]);

  return {
    version: "1.1",
    exportedAt: new Date().toISOString(),
    sports: sports.map((s) => ({
      name: s.name,
      type: s.type,
      slug: s.slug,
      description: s.description,
    })),
    sportsSeasons: sportsSeasons.map((s) => ({
      sportSlug: s.sport.slug,
      name: s.name,
      year: s.year,
      startDate: s.startDate,
      endDate: s.endDate,
      status: s.status,
      scoringType: s.scoringType,
    })),
    participants: participants.map((p) => ({
      sportSlug: p.sportsSeason.sport.slug,
      seasonName: p.sportsSeason.name,
      seasonYear: p.sportsSeason.year,
      name: p.name,
      shortName: p.shortName,
      externalId: p.externalId,
      expectedValue: p.expectedValue,
    })),
    participantExpectedValues: participantEVs.map((ev) => ({
      sportSlug: ev.participant.sportsSeason.sport.slug,
      seasonName: ev.participant.sportsSeason.name,
      seasonYear: ev.participant.sportsSeason.year,
      participantName: ev.participant.name,
      probFirst: ev.probFirst,
      probSecond: ev.probSecond,
      probThird: ev.probThird,
      probFourth: ev.probFourth,
      probFifth: ev.probFifth,
      probSixth: ev.probSixth,
      probSeventh: ev.probSeventh,
      probEighth: ev.probEighth,
      expectedValue: ev.expectedValue,
      source: ev.source,
      sourceOdds: ev.sourceOdds,
      calculatedAt: ev.calculatedAt.toISOString(),
    })),
    participantResults: participantResultsData.map((r) => ({
      sportSlug: r.participant.sportsSeason.sport.slug,
      seasonName: r.participant.sportsSeason.name,
      seasonYear: r.participant.sportsSeason.year,
      participantName: r.participant.name,
      finalPosition: r.finalPosition,
      qualifyingPoints: r.qualifyingPoints,
      notes: r.notes,
    })),
    seasonTemplates: seasonTemplates.map((t) => ({
      name: t.name,
      year: t.year,
      description: t.description,
      isActive: t.isActive,
      sports: t.seasonTemplateSports.map((ts) => ({
        sportSlug: ts.sportsSeason.sport.slug,
        seasonName: ts.sportsSeason.name,
        seasonYear: ts.sportsSeason.year,
      })),
    })),
  };
}

// ─── Import ───────────────────────────────────────────────────────────────────

export async function importSportsDataFromJSON(
  jsonData: string,
  mode: "merge" | "replace" = "merge"
): Promise<{ created: number; updated: number; skipped: number }> {
  const db = database();

  // Validate before touching the database — gives a clear error on bad files
  let importData: ExportData;
  try {
    importData = ImportDataSchema.parse(JSON.parse(jsonData));
  } catch (err) {
    throw new Error(
      `Invalid import file: ${err instanceof Error ? err.message : "unknown validation error"}`, { cause: err }
    );
  }

  // Wrap everything in a transaction so a mid-import failure rolls back cleanly
  return await db.transaction(async (tx) => {
    let created = 0;
    let updated = 0;
    let skipped = 0;

    if (mode === "replace") {
      // Delete in reverse dependency order.
      // participantExpectedValues and participantResults both carry
      // onDelete: "cascade" on their participantId FK, so they are
      // automatically removed when participants is deleted.
      await tx.delete(schema.seasonTemplateSports);
      await tx.delete(schema.seasonTemplates);
      await tx.delete(schema.participants); // cascades to EVs + results
      await tx.delete(schema.seasonSports);
      await tx.delete(schema.sportsSeasons);
      await tx.delete(schema.sports);
    }

    // ID maps used to resolve relationships without further queries
    const sportIdMap = new Map<string, string>();
    const sportsSeasonIdMap = new Map<string, string>();
    // key: `${sportsSeasonId}:${participantName}` — built during participant
    // import so EV/result sections avoid a per-record lookup (fixes N+1)
    const participantIdMap = new Map<string, string>();

    // ── Sports ──────────────────────────────────────────────────────────────

    for (const sport of importData.sports) {
      const existing = await tx.query.sports.findFirst({
        where: eq(schema.sports.slug, sport.slug),
      });

      if (existing) {
        if (mode === "merge") {
          await tx
            .update(schema.sports)
            .set({
              name: sport.name,
              type: sport.type as "team" | "individual",
              description: sport.description,
            })
            .where(eq(schema.sports.slug, sport.slug));
          sportIdMap.set(sport.slug, existing.id);
          updated++;
        }
      } else {
        const [created_] = await tx
          .insert(schema.sports)
          .values({
            name: sport.name,
            type: sport.type as "team" | "individual",
            slug: sport.slug,
            description: sport.description,
          })
          .returning();
        sportIdMap.set(sport.slug, created_.id);
        created++;
      }
    }

    // ── Sports seasons ───────────────────────────────────────────────────────

    for (const season of importData.sportsSeasons) {
      const sportId = sportIdMap.get(season.sportSlug);
      if (!sportId) {
        skipped++;
        continue;
      }

      const seasonKey = `${season.sportSlug}:${season.name}:${season.year}`;

      const existing = await tx.query.sportsSeasons.findFirst({
        where: and(
          eq(schema.sportsSeasons.sportId, sportId),
          eq(schema.sportsSeasons.name, season.name),
          eq(schema.sportsSeasons.year, season.year)
        ),
      });

      if (existing) {
        if (mode === "merge") {
          await tx
            .update(schema.sportsSeasons)
            .set({
              startDate: season.startDate,
              endDate: season.endDate,
              status: season.status as "upcoming" | "active" | "completed",
              scoringType: season.scoringType as
                | "playoffs"
                | "regular_season"
                | "majors",
            })
            .where(eq(schema.sportsSeasons.id, existing.id));
          sportsSeasonIdMap.set(seasonKey, existing.id);
          updated++;
        }
      } else {
        const [created_] = await tx
          .insert(schema.sportsSeasons)
          .values({
            sportId,
            name: season.name,
            year: season.year,
            startDate: season.startDate,
            endDate: season.endDate,
            status: season.status as "upcoming" | "active" | "completed",
            scoringType: season.scoringType as
              | "playoffs"
              | "regular_season"
              | "majors",
          })
          .returning();
        sportsSeasonIdMap.set(seasonKey, created_.id);
        created++;
      }
    }

    // ── Participants ─────────────────────────────────────────────────────────

    for (const participant of importData.participants) {
      const seasonKey = `${participant.sportSlug}:${participant.seasonName}:${participant.seasonYear}`;
      const sportsSeasonId = sportsSeasonIdMap.get(seasonKey);
      if (!sportsSeasonId) {
        skipped++;
        continue;
      }

      const existing = await tx.query.participants.findFirst({
        where: and(
          eq(schema.participants.sportsSeasonId, sportsSeasonId),
          eq(schema.participants.name, participant.name)
        ),
      });

      if (existing) {
        // Update all mutable fields (fixes: merge mode was silently skipping)
        await tx
          .update(schema.participants)
          .set({
            shortName: participant.shortName,
            externalId: participant.externalId,
            expectedValue: String(participant.expectedValue ?? 0),
          })
          .where(eq(schema.participants.id, existing.id));
        participantIdMap.set(`${sportsSeasonId}:${participant.name}`, existing.id);
        updated++;
      } else {
        const [created_] = await tx
          .insert(schema.participants)
          .values({
            sportsSeasonId,
            name: participant.name,
            shortName: participant.shortName,
            externalId: participant.externalId,
            expectedValue: String(participant.expectedValue ?? 0),
          })
          .returning();
        participantIdMap.set(`${sportsSeasonId}:${participant.name}`, created_.id);
        created++;
      }
    }

    // ── Participant expected values ──────────────────────────────────────────

    if (importData.participantExpectedValues) {
      for (const ev of importData.participantExpectedValues) {
        const seasonKey = `${ev.sportSlug}:${ev.seasonName}:${ev.seasonYear}`;
        const sportsSeasonId = sportsSeasonIdMap.get(seasonKey);
        if (!sportsSeasonId) {
          skipped++;
          continue;
        }

        // Use map built during participant import — no extra query needed
        const participantId = participantIdMap.get(
          `${sportsSeasonId}:${ev.participantName}`
        );
        if (!participantId) {
          skipped++;
          continue;
        }

        const existingEV = await tx.query.participantExpectedValues.findFirst({
          where: and(
            eq(schema.participantExpectedValues.participantId, participantId),
            eq(schema.participantExpectedValues.sportsSeasonId, sportsSeasonId)
          ),
        });

        const evValues = {
          probFirst: ev.probFirst,
          probSecond: ev.probSecond,
          probThird: ev.probThird,
          probFourth: ev.probFourth,
          probFifth: ev.probFifth,
          probSixth: ev.probSixth,
          probSeventh: ev.probSeventh,
          probEighth: ev.probEighth,
          expectedValue: ev.expectedValue,
          source: ev.source as
            | "manual"
            | "futures_odds"
            | "elo_simulation"
            | "performance_model"
            | null,
          sourceOdds: ev.sourceOdds,
          // Restore original timestamp when present (fixes: calculatedAt lost)
          ...(ev.calculatedAt
            ? { calculatedAt: new Date(ev.calculatedAt) }
            : {}),
        };

        if (existingEV) {
          if (mode === "merge") {
            await tx
              .update(schema.participantExpectedValues)
              .set({ ...evValues, updatedAt: new Date() })
              .where(eq(schema.participantExpectedValues.id, existingEV.id));
            await tx
              .update(schema.participants)
              .set({ expectedValue: ev.expectedValue })
              .where(eq(schema.participants.id, participantId));
            updated++;
          }
        } else {
          await tx.insert(schema.participantExpectedValues).values({
            participantId,
            sportsSeasonId,
            ...evValues,
          });
          await tx
            .update(schema.participants)
            .set({ expectedValue: ev.expectedValue })
            .where(eq(schema.participants.id, participantId));
          created++;
        }
      }
    }

    // ── Participant results ──────────────────────────────────────────────────

    if (importData.participantResults) {
      for (const result of importData.participantResults) {
        const seasonKey = `${result.sportSlug}:${result.seasonName}:${result.seasonYear}`;
        const sportsSeasonId = sportsSeasonIdMap.get(seasonKey);
        if (!sportsSeasonId) {
          skipped++;
          continue;
        }

        const participantId = participantIdMap.get(
          `${sportsSeasonId}:${result.participantName}`
        );
        if (!participantId) {
          skipped++;
          continue;
        }

        const existingResult = await tx.query.participantResults.findFirst({
          where: and(
            eq(schema.participantResults.participantId, participantId),
            eq(schema.participantResults.sportsSeasonId, sportsSeasonId)
          ),
        });

        const resultValues = {
          finalPosition: result.finalPosition,
          qualifyingPoints: result.qualifyingPoints,
          notes: result.notes,
        };

        if (existingResult) {
          if (mode === "merge") {
            await tx
              .update(schema.participantResults)
              .set({ ...resultValues, updatedAt: new Date() })
              .where(eq(schema.participantResults.id, existingResult.id));
            updated++;
          }
        } else {
          await tx.insert(schema.participantResults).values({
            participantId,
            sportsSeasonId,
            ...resultValues,
          });
          created++;
        }
      }
    }

    // ── Season templates ─────────────────────────────────────────────────────

    for (const template of importData.seasonTemplates) {
      const existing = await tx.query.seasonTemplates.findFirst({
        where: and(
          eq(schema.seasonTemplates.name, template.name),
          eq(schema.seasonTemplates.year, template.year)
        ),
      });

      let templateId: string;

      if (existing) {
        if (mode === "merge") {
          await tx
            .update(schema.seasonTemplates)
            .set({
              description: template.description,
              isActive: template.isActive,
            })
            .where(eq(schema.seasonTemplates.id, existing.id));
          templateId = existing.id;
          updated++;

          // Remove existing template sports to re-add them cleanly
          await tx
            .delete(schema.seasonTemplateSports)
            .where(eq(schema.seasonTemplateSports.templateId, templateId));
        } else {
          continue;
        }
      } else {
        const [created_] = await tx
          .insert(schema.seasonTemplates)
          .values({
            name: template.name,
            year: template.year,
            description: template.description,
            isActive: template.isActive,
          })
          .returning();
        templateId = created_.id;
        created++;
      }

      for (const templateSport of template.sports) {
        const seasonKey = `${templateSport.sportSlug}:${templateSport.seasonName}:${templateSport.seasonYear}`;
        const sportsSeasonId = sportsSeasonIdMap.get(seasonKey);
        if (sportsSeasonId) {
          await tx.insert(schema.seasonTemplateSports).values({
            templateId,
            sportsSeasonId,
          });
        }
      }
    }

    return { created, updated, skipped };
  });
}
