import { NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { z } from "zod";
import { google } from "googleapis";
import { authOptions } from "@/lib/auth";
import { planWorkBlocksIntentSchema } from "@/lib/intent";
import {
  busySlotSchema,
  buildWorkPlan,
  workPlanSchema,
  workingHoursSchema
} from "@/lib/work-planner";

const providerSchema = z.enum(["outlook", "google"]);

const proposeRequestSchema = z.object({
  action: z.literal("propose"),
  provider: providerSchema,
  intent: planWorkBlocksIntentSchema
});

const createRequestSchema = z.object({
  action: z.literal("create"),
  provider: providerSchema,
  idempotencyKey: z.string().min(8),
  plan: workPlanSchema
});

const requestSchema = z.discriminatedUnion("action", [
  proposeRequestSchema,
  createRequestSchema
]);

type GoogleApiError = {
  response?: {
    status?: number;
    data?: unknown;
  };
  message?: string;
};

type IdempotencyEntry = {
  createdAt: number;
  eventIds: string[];
};

declare global {
  // eslint-disable-next-line no-var
  var __workPlanIdempotencyStore: Map<string, IdempotencyEntry> | undefined;
}

const idempotencyStore = globalThis.__workPlanIdempotencyStore ?? new Map<string, IdempotencyEntry>();
if (!globalThis.__workPlanIdempotencyStore) {
  globalThis.__workPlanIdempotencyStore = idempotencyStore;
}

function cleanupIdempotencyStore() {
  const ttlMs = 15 * 60 * 1000;
  const now = Date.now();
  for (const [key, value] of idempotencyStore.entries()) {
    if (now - value.createdAt > ttlMs) {
      idempotencyStore.delete(key);
    }
  }
}

function decodeJwtScopes(accessToken: string): string[] {
  const parts = accessToken.split(".");
  if (parts.length < 2) return [];

  try {
    const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
    const padded = base64 + "=".repeat((4 - (base64.length % 4)) % 4);
    const json = Buffer.from(padded, "base64").toString("utf-8");
    const payload = JSON.parse(json) as { scp?: string };
    return payload.scp ? payload.scp.split(/\s+/).filter(Boolean) : [];
  } catch {
    return [];
  }
}

function requireProviderSession(
  provider: z.infer<typeof providerSchema>,
  sessionProvider?: string
): string | null {
  if (provider === "outlook" && sessionProvider !== "azure-ad") {
    return "Connectez-vous avec Outlook pour utiliser ce provider.";
  }

  if (provider === "google" && sessionProvider !== "google") {
    return "Connectez-vous avec Google pour utiliser ce provider.";
  }

  return null;
}

function buildDateBounds(startIso: string, endIso: string) {
  const start = new Date(startIso);
  const end = new Date(endIso);

  if (Number.isNaN(start.valueOf()) || Number.isNaN(end.valueOf()) || end <= start) {
    throw new Error("Bornes de date invalides.");
  }

  return { start, end };
}

function safeRangeForBusyFetch(deadlineIso: string) {
  const start = new Date();
  const parsedDeadline = new Date(deadlineIso);
  const end =
    Number.isNaN(parsedDeadline.valueOf()) || parsedDeadline <= start
      ? new Date(start.getTime() + 7 * 24 * 60 * 60 * 1000)
      : parsedDeadline;

  return {
    startIso: start.toISOString(),
    endIso: end.toISOString()
  };
}

async function getGoogleGrantedScopes(accessToken: string): Promise<string[]> {
  const response = await fetch(
    `https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=${encodeURIComponent(accessToken)}`,
    { method: "GET" }
  );

  if (!response.ok) return [];

  const payload = (await response.json()) as { scope?: string };
  const scope = payload.scope?.trim();
  return scope ? scope.split(/\s+/).filter(Boolean) : [];
}

async function getWorkingHours(
  provider: z.infer<typeof providerSchema>,
  timezone: string
): Promise<z.infer<typeof workingHoursSchema>> {
  // Provider APIs expose detailed settings with many edge-cases; MVP uses stable defaults.
  if (provider === "outlook" || provider === "google") {
    return workingHoursSchema.parse({
      timezone,
      startHour: 9,
      endHour: 18,
      excludedWeekdays: [0, 6]
    });
  }

  return workingHoursSchema.parse({ timezone });
}

async function getBusySlotsOutlook(input: {
  accessToken: string;
  startIso: string;
  endIso: string;
}) {
  const url = new URL("https://graph.microsoft.com/v1.0/me/calendarView");
  url.searchParams.set("startDateTime", input.startIso);
  url.searchParams.set("endDateTime", input.endIso);
  url.searchParams.set("$top", "200");
  url.searchParams.set("$select", "start,end,showAs,subject");

  const response = await fetch(url.toString(), {
    method: "GET",
    headers: {
      Authorization: `Bearer ${input.accessToken}`,
      Prefer: 'outlook.timezone="UTC"'
    }
  });

  if (!response.ok) {
    const detail = await response.text();
    throw new Error(`Outlook lecture indisponibilites impossible (${response.status}): ${detail}`);
  }

  const payload = (await response.json()) as {
    value?: Array<{
      showAs?: string;
      subject?: string;
      start?: { dateTime?: string };
      end?: { dateTime?: string };
    }>;
  };

  return busySlotSchema
    .array()
    .parse(
      (payload.value ?? [])
        .filter((event) => {
          const isFocus = /focus/i.test(event.subject || "");
          const showAs = (event.showAs || "busy").toLowerCase();
          return isFocus || showAs !== "free";
        })
        .map((event) => ({
          start: event.start?.dateTime ?? "",
          end: event.end?.dateTime ?? ""
        }))
    );
}

async function getBusySlotsGoogle(input: {
  accessToken: string;
  startIso: string;
  endIso: string;
}) {
  const client = new google.auth.OAuth2(
    process.env.GOOGLE_CLIENT_ID,
    process.env.GOOGLE_CLIENT_SECRET
  );
  client.setCredentials({ access_token: input.accessToken });

  const calendar = google.calendar({ version: "v3", auth: client });

  const response = await calendar.events.list({
    calendarId: "primary",
    timeMin: input.startIso,
    timeMax: input.endIso,
    singleEvents: true,
    maxResults: 250,
    orderBy: "startTime"
  });

  const events = response.data.items ?? [];

  return busySlotSchema
    .array()
    .parse(
      events
        .filter((event) => {
          const isFocusEvent =
            event.eventType === "focusTime" || /focus/i.test(event.summary || "");
          const isOpaque = event.transparency !== "transparent";
          return event.status !== "cancelled" && (isOpaque || isFocusEvent);
        })
        .map((event) => {
          const start = event.start?.dateTime || `${event.start?.date}T00:00:00.000Z`;
          const end = event.end?.dateTime || `${event.end?.date}T23:59:00.000Z`;
          return { start, end };
        })
    );
}

async function getBusySlots(input: {
  provider: z.infer<typeof providerSchema>;
  accessToken: string;
  startIso: string;
  endIso: string;
}) {
  if (input.provider === "outlook") {
    return getBusySlotsOutlook(input);
  }

  return getBusySlotsGoogle(input);
}

async function createOutlookBlockEvent(input: {
  accessToken: string;
  title: string;
  startIso: string;
  endIso: string;
  timezone: string;
  description: string;
}) {
  const response = await fetch("https://graph.microsoft.com/v1.0/me/events", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${input.accessToken}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      subject: input.title,
      body: {
        contentType: "text",
        content: input.description
      },
      start: {
        dateTime: input.startIso,
        timeZone: "UTC"
      },
      end: {
        dateTime: input.endIso,
        timeZone: "UTC"
      }
    })
  });

  if (!response.ok) {
    const detail = await response.text();
    throw new Error(`Outlook creation bloc impossible (${response.status}): ${detail}`);
  }

  const payload = (await response.json()) as { id?: string };
  return payload.id || `outlook_${Date.now()}`;
}

async function deleteOutlookEvent(accessToken: string, eventId: string) {
  await fetch(`https://graph.microsoft.com/v1.0/me/events/${eventId}`, {
    method: "DELETE",
    headers: {
      Authorization: `Bearer ${accessToken}`
    }
  });
}

async function createGoogleBlockEvent(input: {
  accessToken: string;
  title: string;
  startIso: string;
  endIso: string;
  timezone: string;
  description: string;
}) {
  const client = new google.auth.OAuth2(
    process.env.GOOGLE_CLIENT_ID,
    process.env.GOOGLE_CLIENT_SECRET
  );
  client.setCredentials({ access_token: input.accessToken });

  const calendar = google.calendar({ version: "v3", auth: client });
  const response = await calendar.events.insert({
    calendarId: "primary",
    requestBody: {
      summary: input.title,
      description: input.description,
      start: {
        dateTime: input.startIso,
        timeZone: input.timezone
      },
      end: {
        dateTime: input.endIso,
        timeZone: input.timezone
      }
    }
  });

  return response.data.id || `google_${Date.now()}`;
}

async function deleteGoogleEvent(accessToken: string, eventId: string) {
  const client = new google.auth.OAuth2(
    process.env.GOOGLE_CLIENT_ID,
    process.env.GOOGLE_CLIENT_SECRET
  );
  client.setCredentials({ access_token: accessToken });

  const calendar = google.calendar({ version: "v3", auth: client });
  await calendar.events.delete({
    calendarId: "primary",
    eventId
  });
}

function parseGoogleError(error: unknown) {
  const parsed = error as GoogleApiError;
  const status = parsed.response?.status ?? 500;

  if (parsed.response?.data && typeof parsed.response.data === "object") {
    const data = parsed.response.data as {
      error?: { message?: string; errors?: Array<{ reason?: string }> };
    };
    const reason = data.error?.errors?.[0]?.reason;
    return `${reason ? `${reason}: ` : ""}${data.error?.message ?? JSON.stringify(data)}`;
  }

  return parsed.message || "Google API error";
}

export async function POST(request: Request) {
  try {
    cleanupIdempotencyStore();

    const session = await getServerSession(authOptions);
    if (!session?.accessToken) {
      return NextResponse.json({ error: "Utilisateur non authentifie." }, { status: 401 });
    }
    const accessToken = session.accessToken;

    const body = await request.json();
    const parsed = requestSchema.parse(body);

    const providerError = requireProviderSession(parsed.provider, session.provider);
    if (providerError) {
      return NextResponse.json({ error: providerError }, { status: 400 });
    }

    if (parsed.provider === "outlook") {
      const scopes = decodeJwtScopes(session.accessToken);
      if (scopes.length > 0 && !scopes.includes("Calendars.ReadWrite")) {
        return NextResponse.json(
          {
            error: "Token Outlook sans Calendars.ReadWrite.",
            details: { required: ["Calendars.ReadWrite"], granted: scopes }
          },
          { status: 403 }
        );
      }
    } else {
      const grantedScopes = await getGoogleGrantedScopes(session.accessToken);
      const hasCalendarScope =
        grantedScopes.includes("https://www.googleapis.com/auth/calendar") ||
        grantedScopes.includes("https://www.googleapis.com/auth/calendar.events");

      if (!hasCalendarScope) {
        return NextResponse.json(
          {
            error: "Scopes Google insuffisants pour lire/ecrire le calendrier.",
            details: {
              required: [
                "https://www.googleapis.com/auth/calendar",
                "https://www.googleapis.com/auth/calendar.events"
              ],
              granted: grantedScopes
            }
          },
          { status: 403 }
        );
      }
    }

    if (parsed.action === "propose") {
      const { startIso, endIso } = safeRangeForBusyFetch(parsed.intent.deadline);
      const { start, end } = buildDateBounds(startIso, endIso);
      const busySlots = await getBusySlots({
        provider: parsed.provider,
        accessToken,
        startIso: start.toISOString(),
        endIso: end.toISOString()
      });

      const workingHours = await getWorkingHours(parsed.provider, parsed.intent.timezone);
      const plan = buildWorkPlan({
        provider: parsed.provider,
        intent: parsed.intent,
        busySlots,
        workingHours,
        now: new Date()
      });

      return NextResponse.json({
        plan,
        stats: {
          busySlotsCount: busySlots.length,
          plannedBlocks: plan.blocks.length
        }
      });
    }

    if (parsed.plan.provider !== parsed.provider) {
      return NextResponse.json(
        { error: "Le plan ne correspond pas au provider selectionne." },
        { status: 400 }
      );
    }

    if (parsed.plan.blocks.length > 24) {
      return NextResponse.json(
        { error: "Limite de securite depassee: max 24 blocs." },
        { status: 400 }
      );
    }

    const existing = idempotencyStore.get(parsed.idempotencyKey);
    if (existing) {
      return NextResponse.json({
        createdCount: existing.eventIds.length,
        eventIds: existing.eventIds,
        idempotent: true,
        provider: parsed.provider
      });
    }

    const createdEventIds: string[] = [];

    try {
      for (const block of parsed.plan.blocks) {
        const description = [
          `Source: ${parsed.plan.source}`,
          `PlanId: ${parsed.plan.planId}`,
          `Task: ${parsed.plan.taskTitle}`
        ].join("\n");

        const eventId =
          parsed.provider === "outlook"
            ? await createOutlookBlockEvent({
                accessToken,
                title: block.title,
                startIso: block.start,
                endIso: block.end,
                timezone: parsed.plan.timezone,
                description
              })
            : await createGoogleBlockEvent({
                accessToken,
                title: block.title,
                startIso: block.start,
                endIso: block.end,
                timezone: parsed.plan.timezone,
                description
              });

        createdEventIds.push(eventId);
      }
    } catch (error) {
      // Best-effort rollback when a batch partially succeeds.
      await Promise.allSettled(
        createdEventIds.map((eventId) =>
          parsed.provider === "outlook"
            ? deleteOutlookEvent(accessToken, eventId)
            : deleteGoogleEvent(accessToken, eventId)
        )
      );

      if (parsed.provider === "google") {
        return NextResponse.json(
          { error: `Echec creation lot Google: ${parseGoogleError(error)}` },
          { status: 400 }
        );
      }

      const message = error instanceof Error ? error.message : "Echec creation lot Outlook.";
      return NextResponse.json({ error: message }, { status: 400 });
    }

    idempotencyStore.set(parsed.idempotencyKey, {
      createdAt: Date.now(),
      eventIds: createdEventIds
    });

    return NextResponse.json({
      createdCount: createdEventIds.length,
      eventIds: createdEventIds,
      idempotent: false,
      provider: parsed.provider
    });
  } catch (error) {
    const message = error instanceof Error ? error.message : "Echec planification travail.";
    return NextResponse.json({ error: message }, { status: 400 });
  }
}
