import { NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { z } from "zod";
import { google } from "googleapis";
import { authOptions } from "@/lib/auth";
import { createCalendarEventIntentSchema } from "@/lib/intent";

const requestSchema = z.object({
  intent: createCalendarEventIntentSchema,
  provider: z.enum(["outlook", "google"])
});

type OutlookGraphError = {
  status: number;
  detail: string;
};

type GoogleApiError = {
  response?: {
    status?: number;
    data?: unknown;
    headers?: Record<string, string | string[] | undefined>;
  };
  message?: string;
};

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 scopes = payload.scope?.trim();
  if (!scopes) return [];

  return scopes.split(/\s+/).filter(Boolean);
}

async function verifyGoogleCalendarAccess(accessToken: string): Promise<{
  ok: boolean;
  status?: number;
  detail?: string;
}> {
  const response = await fetch(
    "https://www.googleapis.com/calendar/v3/users/me/calendarList?maxResults=1",
    {
      method: "GET",
      headers: {
        Authorization: `Bearer ${accessToken}`
      }
    }
  );

  if (response.ok) {
    return { ok: true };
  }

  const raw = (await response.text()).trim();
  if (!raw) {
    return {
      ok: false,
      status: response.status,
      detail: `status=${response.status} ${response.statusText}`
    };
  }

  try {
    const parsed = JSON.parse(raw) as {
      error?: { code?: number; message?: string; errors?: Array<{ reason?: string }> };
    };
    const reason = parsed.error?.errors?.[0]?.reason;
    const reasonPrefix = reason ? `${reason}: ` : "";
    return {
      ok: false,
      status: response.status,
      detail: `${reasonPrefix}${parsed.error?.message ?? raw}`
    };
  } catch {
    return {
      ok: false,
      status: response.status,
      detail: raw
    };
  }
}

function buildDateBounds(startIso: string, durationMinutes: number) {
  const start = new Date(startIso);
  if (Number.isNaN(start.valueOf())) {
    throw new Error("Date de debut invalide.");
  }
  const end = new Date(start.getTime() + durationMinutes * 60 * 1000);
  return { start, end };
}

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 decodeJwtAudience(accessToken: string): string | null {
  const parts = accessToken.split(".");
  if (parts.length < 2) return null;

  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 { aud?: string };
    return payload.aud ?? null;
  } catch {
    return null;
  }
}

async function verifyOutlookTokenAccess(accessToken: string): Promise<{
  ok: boolean;
  status?: number;
  detail?: string;
}> {
  const response = await fetch("https://graph.microsoft.com/v1.0/me?$select=id", {
    method: "GET",
    headers: {
      Authorization: `Bearer ${accessToken}`
    }
  });

  if (response.ok) {
    return { ok: true };
  }

  const raw = (await response.text()).trim();
  if (!raw) {
    return {
      ok: false,
      status: response.status,
      detail: `status=${response.status} ${response.statusText}`
    };
  }

  try {
    const parsed = JSON.parse(raw) as { error?: { code?: string; message?: string } };
    const code = parsed.error?.code ? `${parsed.error.code}: ` : "";
    return {
      ok: false,
      status: response.status,
      detail: `${code}${parsed.error?.message ?? raw}`
    };
  } catch {
    return {
      ok: false,
      status: response.status,
      detail: raw
    };
  }
}

async function verifyOutlookCalendarAccess(accessToken: string): Promise<{
  ok: boolean;
  status?: number;
  detail?: string;
}> {
  const response = await fetch(
    "https://graph.microsoft.com/v1.0/me/calendar/events?$top=1",
    {
      method: "GET",
      headers: {
        Authorization: `Bearer ${accessToken}`
      }
    }
  );

  if (response.ok) {
    return { ok: true };
  }

  const raw = (await response.text()).trim();
  const authHeader = response.headers.get("www-authenticate");
  const authMeta = authHeader ? ` auth=${authHeader}` : "";
  if (!raw) {
    return {
      ok: false,
      status: response.status,
      detail: `status=${response.status} ${response.statusText}${authMeta}`
    };
  }

  try {
    const parsed = JSON.parse(raw) as { error?: { code?: string; message?: string } };
    const code = parsed.error?.code ? `${parsed.error.code}: ` : "";
    return {
      ok: false,
      status: response.status,
      detail: `${code}${parsed.error?.message ?? raw}${authMeta}`
    };
  } catch {
    return {
      ok: false,
      status: response.status,
      detail: `${raw}${authMeta}`
    };
  }
}

async function postOutlookEvent(
  accessToken: string,
  body: unknown,
  path = "/me/events"
) {
  const response = await fetch(`https://graph.microsoft.com/v1.0${path}`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${accessToken}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify(body)
  });

  if (response.ok) {
    return (await response.json()) as { id: string };
  }

  const rawDetail = await response.text();
  let detail = rawDetail.trim();
  const authHeader = response.headers.get("www-authenticate");
  const authMeta = authHeader ? ` auth=${authHeader}` : "";

  if (detail) {
    try {
      const parsed = JSON.parse(detail) as {
        error?: {
          message?: string;
          code?: string;
          innerError?: { date?: string; requestId?: string; "client-request-id"?: string };
        };
      };
      const code = parsed.error?.code ? `${parsed.error.code}: ` : "";
      const innerRequestId =
        parsed.error?.innerError?.requestId ||
        parsed.error?.innerError?.["client-request-id"];
      const innerMeta = innerRequestId ? ` request-id=${innerRequestId}` : "";
      detail = `${code}${parsed.error?.message ?? detail}${innerMeta}${authMeta}`;
    } catch {
      // Keep raw text when response body is not JSON.
      detail = `${detail}${authMeta}`;
    }
  } else {
    const requestId =
      response.headers.get("request-id") ||
      response.headers.get("x-ms-request-id") ||
      response.headers.get("client-request-id");
    const requestMeta = requestId ? ` request-id=${requestId}` : "";
    detail = `status=${response.status} ${response.statusText}${requestMeta}${authMeta}`;
  }

  throw {
    status: response.status,
    detail
  } satisfies OutlookGraphError;
}

async function createOutlookEvent(
  accessToken: string,
  intent: z.infer<typeof createCalendarEventIntentSchema>
) {
  const { start, end } = buildDateBounds(
    intent.start_datetime,
    intent.duration_minutes
  );

  const basePayload = {
    subject: intent.title,
    body: {
      contentType: "text",
      content: intent.notes ?? ""
    },
    start: {
      dateTime: start.toISOString().replace("Z", ""),
      timeZone: "UTC"
    },
    end: {
      dateTime: end.toISOString().replace("Z", ""),
      timeZone: "UTC"
    },
    attendees: intent.attendees.map((email) => ({
      emailAddress: { address: email },
      type: "required"
    })),
    location: intent.location ? { displayName: intent.location } : undefined
  };

  const writePaths = ["/me/calendar/events", "/me/events"] as const;

  for (const path of writePaths) {
    try {
      const payload = await postOutlookEvent(
        accessToken,
        {
          ...basePayload,
          isOnlineMeeting: intent.online_meeting,
          onlineMeetingProvider: intent.online_meeting ? "teamsForBusiness" : undefined
        },
        path
      );
      return payload.id;
    } catch (error) {
      const graphError = error as OutlookGraphError;
      const canRetryWithoutOnlineMeeting =
        intent.online_meeting &&
        (graphError.status === 400 || graphError.status === 401 || graphError.status === 403);

      if (!canRetryWithoutOnlineMeeting) {
        if (path === writePaths[writePaths.length - 1]) {
          throw new Error(`Graph error (${graphError.status}): ${graphError.detail}`);
        }
        continue;
      }

      try {
        const retryPayload = await postOutlookEvent(
          accessToken,
          {
            ...basePayload,
            isOnlineMeeting: false
          },
          path
        );
        return retryPayload.id;
      } catch (retryError) {
        const retryGraphError = retryError as OutlookGraphError;
        if (path === writePaths[writePaths.length - 1]) {
          throw new Error(
            `Graph error (${retryGraphError.status}): ${retryGraphError.detail}`
          );
        }
      }
    }
  }

  throw new Error("Graph error (500): Echec de creation evenement Outlook.");
}

async function createGoogleEvent(
  accessToken: string,
  intent: z.infer<typeof createCalendarEventIntentSchema>
) {
  const { start, end } = buildDateBounds(
    intent.start_datetime,
    intent.duration_minutes
  );

  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 });

  const baseRequest = {
    summary: intent.title,
    description: intent.notes ?? undefined,
    start: {
      dateTime: start.toISOString(),
      timeZone: intent.timezone
    },
    end: {
      dateTime: end.toISOString(),
      timeZone: intent.timezone
    },
    attendees: intent.attendees.map((email) => ({ email })),
    location: intent.location ?? undefined
  };

  const insert = async (includeConference: boolean) => {
    return calendar.events.insert({
      calendarId: "primary",
      requestBody: {
        ...baseRequest,
        conferenceData: includeConference
          ? {
              createRequest: {
                requestId: `smartcal-${Date.now()}`,
                conferenceSolutionKey: { type: "hangoutsMeet" }
              }
            }
          : undefined
      },
      conferenceDataVersion: includeConference ? 1 : 0
    });
  };

  const formatGoogleError = (error: unknown) => {
    const gError = error as GoogleApiError;
    const status = gError.response?.status ?? 500;
    const data = gError.response?.data;

    if (data && typeof data === "object") {
      const typed = data as {
        error?: { message?: string; errors?: Array<{ reason?: string }> };
      };
      const reason = typed.error?.errors?.[0]?.reason;
      const reasonPrefix = reason ? `${reason}: ` : "";
      return {
        status,
        detail: `${reasonPrefix}${typed.error?.message ?? JSON.stringify(data)}`
      };
    }

    return {
      status,
      detail: gError.message || "Google Calendar API error"
    };
  };

  try {
    const response = await insert(intent.online_meeting);
    return response.data.id ?? `google_${Date.now()}`;
  } catch (error) {
    const parsed = formatGoogleError(error);
    const canRetryWithoutConference =
      intent.online_meeting && (parsed.status === 400 || parsed.status === 403);

    if (!canRetryWithoutConference) {
      throw new Error(`Google Calendar error (${parsed.status}): ${parsed.detail}`);
    }

    const retry = await insert(false);
    return retry.data.id ?? `google_${Date.now()}`;
  }
}

export async function POST(request: Request) {
  try {
    const session = await getServerSession(authOptions);
    if (!session?.accessToken) {
      return NextResponse.json(
        { error: "Utilisateur non authentifie." },
        { status: 401 }
      );
    }

    const body = await request.json();
    const { intent, provider } = requestSchema.parse(body);

    if (provider === "outlook" && session.provider !== "azure-ad") {
      return NextResponse.json(
        { error: "Connectez-vous avec Outlook pour ecrire sur Outlook." },
        { status: 400 }
      );
    }

    if (provider === "google" && session.provider !== "google") {
      return NextResponse.json(
        { error: "Connectez-vous avec Google pour ecrire sur Google Calendar." },
        { status: 400 }
      );
    }

    if (provider === "outlook") {
      const scopes = decodeJwtScopes(session.accessToken);
      const audience = decodeJwtAudience(session.accessToken);

      if (audience && audience !== "https://graph.microsoft.com" && audience !== "00000003-0000-0000-c000-000000000000") {
        return NextResponse.json(
          {
            error:
              "Token Outlook non destine a Microsoft Graph. Reconnectez Outlook pour recuperer un token Graph valide.",
            details: {
              expected_aud: [
                "https://graph.microsoft.com",
                "00000003-0000-0000-c000-000000000000"
              ],
              granted_aud: audience
            }
          },
          { status: 403 }
        );
      }

      if (scopes.length > 0 && !scopes.includes("Calendars.ReadWrite")) {
        return NextResponse.json(
          {
            error:
              "Token Outlook sans Calendars.ReadWrite. Reconnectez Outlook et revalidez le consentement Graph.",
            details: {
              required: ["Calendars.ReadWrite"],
              granted: scopes
            }
          },
          { status: 403 }
        );
      }

      const accessCheck = await verifyOutlookTokenAccess(session.accessToken);
      if (!accessCheck.ok) {
        return NextResponse.json(
          {
            error: "Token Outlook invalide ou consentement Graph insuffisant.",
            details: {
              graph_status: accessCheck.status,
              graph_error: accessCheck.detail
            }
          },
          { status: 403 }
        );
      }

      const calendarAccessCheck = await verifyOutlookCalendarAccess(session.accessToken);
      if (!calendarAccessCheck.ok) {
        const likelyTenantOrMailboxIssue =
          calendarAccessCheck.status === 401 &&
          !!audience &&
          (audience === "https://graph.microsoft.com" ||
            audience === "00000003-0000-0000-c000-000000000000") &&
          scopes.includes("Calendars.ReadWrite");

        return NextResponse.json(
          {
            error: likelyTenantOrMailboxIssue
              ? "Acces calendrier Outlook refuse. Token valide mais boite/tenant non autorise pour Calendar."
              : "Acces calendrier Outlook refuse par Graph.",
            details: {
              graph_status: calendarAccessCheck.status,
              graph_error: calendarAccessCheck.detail,
              token_scopes: scopes,
              token_aud: audience,
              hint: likelyTenantOrMailboxIssue
                ? "Reconnectez Outlook apres passage en tenant common, et verifiez que le compte a une boite Outlook active."
                : undefined
            }
          },
          { status: 403 }
        );
      }
    }

    if (provider === "google") {
      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. Deconnectez-vous puis reconnectez Google pour redonner les permissions Calendar.",
            details: {
              required: [
                "https://www.googleapis.com/auth/calendar",
                "https://www.googleapis.com/auth/calendar.events"
              ],
              granted: grantedScopes
            }
          },
          { status: 403 }
        );
      }

      const accessCheck = await verifyGoogleCalendarAccess(session.accessToken);
      if (!accessCheck.ok) {
        return NextResponse.json(
          {
            error: "Token Google invalide ou consentement Calendar insuffisant.",
            details: {
              google_status: accessCheck.status,
              google_error: accessCheck.detail
            }
          },
          { status: 403 }
        );
      }
    }

    const eventId =
      provider === "outlook"
        ? await createOutlookEvent(session.accessToken, intent)
        : await createGoogleEvent(session.accessToken, intent);

    return NextResponse.json({
      eventId,
      provider
    });
  } catch (error) {
    const message =
      error instanceof Error ? error.message : "Impossible de creer l'evenement.";

    if (/insufficient authentication scopes/i.test(message)) {
      return NextResponse.json(
        {
          error:
            "Scopes Google insuffisants. Deconnectez-vous puis reconnectez Google pour redonner les permissions Calendar."
        },
        { status: 403 }
      );
    }

    return NextResponse.json({ error: message }, { status: 400 });
  }
}
