import { decryptSecret, encryptSecret } from "@/lib/ai/crypto";
import { createAdminClient } from "@/lib/supabase/admin";
import type {
  GmailMessageSummary,
  GoogleDriveFileSummary,
  IntegrationProvider,
} from "@/types";

type GoogleWorkspaceProvider = Extract<IntegrationProvider, "gmail" | "google_drive">;
type GoogleWorkspaceStoredConnection = {
  id: string;
  secret_encrypted: string;
};
type GmailPayloadPart = {
  mimeType?: string;
  filename?: string;
  body?: { data?: string; attachmentId?: string; size?: number };
  parts?: GmailPayloadPart[];
};
interface GmailAttachmentReference {
  attachmentId: string;
  fileName: string;
  mimeType: string | null;
}

interface GoogleWorkspaceCredentials {
  access_token: string;
  refresh_token?: string;
  expiry_date?: number;
  scope?: string;
  token_type?: string;
  connected_email?: string | null;
}

export interface GmailMessageDetail extends GmailMessageSummary {
  textBody: string;
  htmlBody: string | null;
  attachmentNames: string[];
  attachments: GmailAttachmentReference[];
}

interface GoogleWorkspaceStatePayload {
  userId: string;
  teamId: string | null;
  issuedAt: number;
}

const REDIRECT_ENV_BY_PROVIDER: Record<GoogleWorkspaceProvider, string> = {
  gmail: "GOOGLE_GMAIL_REDIRECT_URI",
  google_drive: "GOOGLE_DRIVE_REDIRECT_URI",
};

const REDIRECT_PATH_BY_PROVIDER: Record<GoogleWorkspaceProvider, string> = {
  gmail: "/api/integrations/gmail/callback",
  google_drive: "/api/integrations/google-drive/callback",
};

function getGoogleWorkspaceClientConfig() {
  const clientId = process.env.GOOGLE_CLIENT_ID;
  const clientSecret = process.env.GOOGLE_CLIENT_SECRET;

  if (!clientId || !clientSecret) {
    throw new Error("Missing GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET");
  }

  return { clientId, clientSecret };
}

export function getGoogleWorkspaceRedirectUri(
  provider: GoogleWorkspaceProvider,
  origin?: string
) {
  const envValue = process.env[REDIRECT_ENV_BY_PROVIDER[provider]];
  const base = (envValue || process.env.NEXT_PUBLIC_APP_URL || origin || "").replace(/\/$/, "");
  if (!base) {
    throw new Error(
      `Missing NEXT_PUBLIC_APP_URL or ${REDIRECT_ENV_BY_PROVIDER[provider]}`
    );
  }

  const path = REDIRECT_PATH_BY_PROVIDER[provider];
  return base.endsWith(path) ? base : `${base}${path}`;
}

export function createGoogleWorkspaceState(payload: GoogleWorkspaceStatePayload) {
  return encodeURIComponent(encryptSecret(JSON.stringify(payload)));
}

export function parseGoogleWorkspaceState(value: string) {
  return JSON.parse(
    decryptSecret(decodeURIComponent(value))
  ) as GoogleWorkspaceStatePayload;
}

export function buildGoogleWorkspaceConnectUrl(input: {
  state: string;
  redirectUri: string;
  scopes: string[];
}) {
  const { clientId } = getGoogleWorkspaceClientConfig();
  const params = new URLSearchParams({
    client_id: clientId,
    redirect_uri: input.redirectUri,
    response_type: "code",
    access_type: "offline",
    include_granted_scopes: "true",
    prompt: "consent",
    scope: input.scopes.join(" "),
    state: input.state,
  });

  return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
}

export async function exchangeGoogleWorkspaceCode(input: {
  code: string;
  redirectUri: string;
}) {
  const { clientId, clientSecret } = getGoogleWorkspaceClientConfig();
  const params = new URLSearchParams({
    code: input.code,
    client_id: clientId,
    client_secret: clientSecret,
    redirect_uri: input.redirectUri,
    grant_type: "authorization_code",
  });

  const response = await fetch("https://oauth2.googleapis.com/token", {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: params.toString(),
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(errorText || "Google token exchange failed");
  }

  const token = (await response.json()) as {
    access_token: string;
    refresh_token?: string;
    expires_in: number;
    scope?: string;
    token_type?: string;
  };

  return {
    access_token: token.access_token,
    refresh_token: token.refresh_token,
    expiry_date: Date.now() + token.expires_in * 1000,
    scope: token.scope,
    token_type: token.token_type,
  } satisfies GoogleWorkspaceCredentials;
}

function parseCredentials(secretEncrypted: string) {
  return JSON.parse(decryptSecret(secretEncrypted)) as GoogleWorkspaceCredentials;
}

function serializeCredentials(credentials: GoogleWorkspaceCredentials) {
  return encryptSecret(JSON.stringify(credentials));
}

async function refreshGoogleWorkspaceAccessToken(
  admin: ReturnType<typeof createAdminClient>,
  connection: GoogleWorkspaceStoredConnection
) {
  const { clientId, clientSecret } = getGoogleWorkspaceClientConfig();
  const credentials = parseCredentials(connection.secret_encrypted);

  if (
    credentials.access_token &&
    credentials.expiry_date &&
    credentials.expiry_date > Date.now() + 60_000
  ) {
    return credentials;
  }

  if (!credentials.refresh_token) {
    throw new Error("Google Workspace refresh token missing");
  }

  const params = new URLSearchParams({
    client_id: clientId,
    client_secret: clientSecret,
    refresh_token: credentials.refresh_token,
    grant_type: "refresh_token",
  });

  const response = await fetch("https://oauth2.googleapis.com/token", {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: params.toString(),
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(errorText || "Google token refresh failed");
  }

  const token = (await response.json()) as {
    access_token: string;
    expires_in: number;
    scope?: string;
    token_type?: string;
  };

  const nextCredentials: GoogleWorkspaceCredentials = {
    ...credentials,
    access_token: token.access_token,
    expiry_date: Date.now() + token.expires_in * 1000,
    scope: token.scope || credentials.scope,
    token_type: token.token_type || credentials.token_type,
  };

  await admin
    .from("integration_connections")
    .update({
      secret_encrypted: serializeCredentials(nextCredentials),
    })
    .eq("id", connection.id);

  return nextCredentials;
}

async function googleWorkspaceFetch<T>(
  admin: ReturnType<typeof createAdminClient>,
  connection: GoogleWorkspaceStoredConnection,
  input: {
    baseUrl: string;
    path: string;
    method?: "GET" | "POST";
    body?: Record<string, unknown>;
  }
) {
  const credentials = await refreshGoogleWorkspaceAccessToken(admin, connection);
  const response = await fetch(`${input.baseUrl}${input.path}`, {
    method: input.method || "GET",
    headers: {
      Authorization: `Bearer ${credentials.access_token}`,
      "Content-Type": "application/json",
      "User-Agent": "Mindavio-GoogleWorkspace/1.0",
    },
    body: input.body ? JSON.stringify(input.body) : undefined,
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(errorText || "Google Workspace request failed");
  }

  if (response.status === 204) {
    return null as T;
  }

  return (await response.json()) as T;
}

function getHeaderValue(
  headers: Array<{ name?: string; value?: string }> | undefined,
  headerName: string
) {
  return (
    headers?.find((header) => header.name?.toLowerCase() === headerName.toLowerCase())?.value ||
    null
  );
}

function decodeBase64Url(value: string | null | undefined) {
  if (!value) return "";

  const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
  const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
  return Buffer.from(padded, "base64").toString("utf8");
}

function decodeBase64UrlToBuffer(value: string | null | undefined) {
  if (!value) return Buffer.alloc(0);

  const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
  const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
  return Buffer.from(padded, "base64");
}

function encodeBase64Url(value: string) {
  return Buffer.from(value, "utf8")
    .toString("base64")
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/g, "");
}

function stripHtml(html: string) {
  return html
    .replace(/<style[\s\S]*?<\/style>/gi, "")
    .replace(/<script[\s\S]*?<\/script>/gi, "")
    .replace(/<br\s*\/?>/gi, "\n")
    .replace(/<\/p>/gi, "\n\n")
    .replace(/<[^>]+>/g, " ")
    .replace(/&nbsp;/g, " ")
    .replace(/&amp;/g, "&")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">")
    .replace(/\r/g, "")
    .replace(/[ \t]+\n/g, "\n")
    .replace(/\n{3,}/g, "\n\n")
    .trim();
}

function extractMessageBodies(
  part: GmailPayloadPart | undefined,
  bucket: {
    plain: string[];
    html: string[];
    attachmentNames: string[];
    attachments: GmailAttachmentReference[];
  }
) {
  if (!part) return;

  if (part.filename) {
    bucket.attachmentNames.push(part.filename);
  }

  if (part.body?.attachmentId && part.filename) {
    bucket.attachments.push({
      attachmentId: part.body.attachmentId,
      fileName: part.filename,
      mimeType: part.mimeType || null,
    });
  }

  const mimeType = part.mimeType || "";
  const body = decodeBase64Url(part.body?.data);

  if (mimeType === "text/plain" && body.trim()) {
    bucket.plain.push(body);
  } else if (mimeType === "text/html" && body.trim()) {
    bucket.html.push(body);
  }

  if (Array.isArray(part.parts)) {
    part.parts.forEach((child) => extractMessageBodies(child, bucket));
  }
}

export async function getGoogleGmailProfile(
  admin: ReturnType<typeof createAdminClient>,
  connection: GoogleWorkspaceStoredConnection
) {
  const payload = await googleWorkspaceFetch<{ emailAddress?: string }>(admin, connection, {
    baseUrl: "https://gmail.googleapis.com/gmail/v1",
    path: "/users/me/profile",
  });

  return {
    connectedEmail: payload.emailAddress || null,
  };
}

export async function listGmailMessages(
  admin: ReturnType<typeof createAdminClient>,
  connection: GoogleWorkspaceStoredConnection,
  maxResults = 12
) {
  const params = new URLSearchParams({
    maxResults: String(Math.min(Math.max(maxResults, 1), 20)),
    q: "-in:trash",
  });

  const payload = await googleWorkspaceFetch<{
    messages?: Array<{ id: string; threadId?: string }>;
  }>(admin, connection, {
    baseUrl: "https://gmail.googleapis.com/gmail/v1",
    path: `/users/me/messages?${params.toString()}`,
  });

  const detailResults = await Promise.allSettled(
    (payload.messages || []).map(async (message) =>
      googleWorkspaceFetch<{
        id: string;
        threadId?: string;
        snippet?: string;
        internalDate?: string;
        payload?: {
          headers?: Array<{ name?: string; value?: string }>;
        };
      }>(admin, connection, {
        baseUrl: "https://gmail.googleapis.com/gmail/v1",
        path:
          `/users/me/messages/${encodeURIComponent(message.id)}` +
          "?format=metadata&metadataHeaders=Subject&metadataHeaders=From&metadataHeaders=Date",
      })
    )
  );

  return detailResults
    .filter(
      (
        result
      ): result is PromiseFulfilledResult<{
        id: string;
        threadId?: string;
        snippet?: string;
        internalDate?: string;
        payload?: { headers?: Array<{ name?: string; value?: string }> };
      }> => result.status === "fulfilled"
    )
    .map((result) => {
      const message = result.value;
      const headers = message.payload?.headers;
      const internalDate = message.internalDate
        ? new Date(Number(message.internalDate)).toISOString()
        : null;

      return {
        id: message.id,
        threadId: message.threadId || null,
        subject: getHeaderValue(headers, "Subject"),
        from: getHeaderValue(headers, "From"),
        date: getHeaderValue(headers, "Date"),
        snippet: message.snippet || "",
        internalDate,
        webUrl: `https://mail.google.com/mail/u/0/#all/${encodeURIComponent(message.id)}`,
      } satisfies GmailMessageSummary;
    });
}

export async function getGmailMessageDetail(
  admin: ReturnType<typeof createAdminClient>,
  connection: GoogleWorkspaceStoredConnection,
  messageId: string
) {
  const message = await googleWorkspaceFetch<{
    id: string;
    threadId?: string;
    snippet?: string;
    internalDate?: string;
    payload?: GmailPayloadPart & {
      headers?: Array<{ name?: string; value?: string }>;
    };
  }>(admin, connection, {
    baseUrl: "https://gmail.googleapis.com/gmail/v1",
    path: `/users/me/messages/${encodeURIComponent(messageId)}?format=full`,
  });

  const bucket = {
    plain: [] as string[],
    html: [] as string[],
    attachmentNames: [] as string[],
    attachments: [] as GmailAttachmentReference[],
  };
  extractMessageBodies(message.payload, bucket);

  const headers = message.payload?.headers;
  const htmlBody = bucket.html.length > 0 ? bucket.html.join("\n\n") : null;
  const textBody =
    bucket.plain.join("\n\n").trim() ||
    (htmlBody ? stripHtml(htmlBody) : "") ||
    message.snippet ||
    "";

  return {
    id: message.id,
    threadId: message.threadId || null,
    subject: getHeaderValue(headers, "Subject"),
    from: getHeaderValue(headers, "From"),
    date: getHeaderValue(headers, "Date"),
    snippet: message.snippet || "",
    internalDate: message.internalDate
      ? new Date(Number(message.internalDate)).toISOString()
      : null,
    webUrl: `https://mail.google.com/mail/u/0/#all/${encodeURIComponent(message.id)}`,
    textBody,
    htmlBody,
    attachmentNames: Array.from(new Set(bucket.attachmentNames.filter(Boolean))),
    attachments: bucket.attachments,
  } satisfies GmailMessageDetail;
}

export async function downloadGmailAttachment(
  admin: ReturnType<typeof createAdminClient>,
  connection: GoogleWorkspaceStoredConnection,
  input: {
    messageId: string;
    attachment: GmailAttachmentReference;
  }
) {
  const payload = await googleWorkspaceFetch<{
    data?: string;
    size?: number;
  }>(admin, connection, {
    baseUrl: "https://gmail.googleapis.com/gmail/v1",
    path:
      `/users/me/messages/${encodeURIComponent(input.messageId)}/attachments/` +
      encodeURIComponent(input.attachment.attachmentId),
  });

  return {
    buffer: decodeBase64UrlToBuffer(payload.data),
    fileName: input.attachment.fileName,
    mimeType: input.attachment.mimeType,
  };
}

export async function createGmailDraft(
  admin: ReturnType<typeof createAdminClient>,
  connection: GoogleWorkspaceStoredConnection & {
    user_id: string;
    team_id: string | null;
    provider: IntegrationProvider;
    config?: Record<string, unknown>;
  },
  input: {
    attachments?: Array<{
      content: Buffer;
      fileName: string;
      mimeType: string;
    }>;
    eventType: string;
    payload: unknown;
    to?: string | null;
    subject: string;
    body: string;
  }
) {
  const connectedEmail =
    connection.config && typeof connection.config.connectedEmail === "string"
      ? connection.config.connectedEmail
      : null;
  const to = input.to?.trim() || connectedEmail;

  if (!to) {
    throw new Error("No recipient available for Gmail draft. Reconnect Gmail or provide a To value.");
  }

  const attachments = input.attachments || [];
  const rawMessage =
    attachments.length === 0
      ? [
          `To: ${to}`,
          `Subject: ${input.subject}`,
          "MIME-Version: 1.0",
          'Content-Type: text/plain; charset="UTF-8"',
          "Content-Transfer-Encoding: 7bit",
          "",
          input.body,
        ].join("\r\n")
      : [
          `To: ${to}`,
          `Subject: ${input.subject}`,
          "MIME-Version: 1.0",
          'Content-Type: multipart/mixed; boundary="mindavio-invoice-boundary"',
          "",
          "--mindavio-invoice-boundary",
          'Content-Type: text/plain; charset="UTF-8"',
          "Content-Transfer-Encoding: 7bit",
          "",
          input.body,
          ...attachments.flatMap((attachment) => [
            "--mindavio-invoice-boundary",
            `Content-Type: ${attachment.mimeType}; name="${attachment.fileName}"`,
            "Content-Transfer-Encoding: base64",
            `Content-Disposition: attachment; filename="${attachment.fileName}"`,
            "",
            attachment.content.toString("base64").replace(/(.{76})/g, "$1\r\n"),
          ]),
          "--mindavio-invoice-boundary--",
          "",
        ].join("\r\n");

  let responseStatus: number | null = null;
  let responseBody: string | null = null;
  let success = false;
  let draftId: string | null = null;

  try {
    const payload = await googleWorkspaceFetch<{
      id?: string;
      message?: { id?: string };
    }>(admin, connection, {
      baseUrl: "https://gmail.googleapis.com/gmail/v1",
      path: "/users/me/drafts",
      method: "POST",
      body: {
        message: {
          raw: encodeBase64Url(rawMessage),
        },
      },
    });

    draftId = payload.id || null;
    responseStatus = 200;
    responseBody = draftId ? `draft:${draftId}` : "draft_created";
    success = true;
  } catch (error) {
    responseBody =
      error instanceof Error ? error.message.slice(0, 4000) : "Gmail draft creation failed";
  }

  await admin.from("integration_deliveries").insert({
    connection_id: connection.id,
    user_id: connection.user_id,
    team_id: connection.team_id,
    provider: connection.provider,
    event_type: input.eventType,
    payload: input.payload,
    response_status: responseStatus,
    response_body: responseBody,
    success,
  });

  await admin
    .from("integration_connections")
    .update({
      last_delivery_at: new Date().toISOString(),
      last_response_status: responseStatus,
      last_error: success ? null : responseBody,
    })
    .eq("id", connection.id);

  if (!success) {
    throw new Error(responseBody || "Gmail draft could not be created.");
  }

  return {
    draftId,
    recipient: to,
  };
}

export async function getGoogleDriveProfile(
  admin: ReturnType<typeof createAdminClient>,
  connection: GoogleWorkspaceStoredConnection
) {
  const payload = await googleWorkspaceFetch<{
    user?: {
      emailAddress?: string;
    };
  }>(admin, connection, {
    baseUrl: "https://www.googleapis.com/drive/v3",
    path: "/about?fields=user(emailAddress)",
  });

  return {
    connectedEmail: payload.user?.emailAddress || null,
  };
}

export async function listGoogleDriveFiles(
  admin: ReturnType<typeof createAdminClient>,
  connection: GoogleWorkspaceStoredConnection,
  pageSize = 12
) {
  const params = new URLSearchParams({
    pageSize: String(Math.min(Math.max(pageSize, 1), 20)),
    orderBy: "modifiedTime desc",
    includeItemsFromAllDrives: "true",
    supportsAllDrives: "true",
    q: "trashed = false",
    fields:
      "files(id,name,mimeType,webViewLink,iconLink,modifiedTime,owners(displayName,emailAddress))",
  });

  const payload = await googleWorkspaceFetch<{
    files?: Array<{
      id: string;
      name?: string;
      mimeType?: string;
      webViewLink?: string;
      iconLink?: string;
      modifiedTime?: string;
      owners?: Array<{ displayName?: string; emailAddress?: string }>;
    }>;
  }>(admin, connection, {
    baseUrl: "https://www.googleapis.com/drive/v3",
    path: `/files?${params.toString()}`,
  });

  return (payload.files || []).map((file) => ({
    id: file.id,
    name: file.name || "Untitled file",
    mimeType: file.mimeType || "application/octet-stream",
    webViewLink: file.webViewLink || null,
    iconLink: file.iconLink || null,
    modifiedTime: file.modifiedTime || null,
    owners:
      file.owners?.map((owner) => owner.displayName || owner.emailAddress || "Unknown owner") ||
      [],
  })) satisfies GoogleDriveFileSummary[];
}

function ensureFileExtension(name: string, extension: string) {
  return name.toLowerCase().endsWith(extension) ? name : `${name}${extension}`;
}

export async function downloadGoogleDriveFile(
  admin: ReturnType<typeof createAdminClient>,
  connection: GoogleWorkspaceStoredConnection,
  file: Pick<GoogleDriveFileSummary, "id" | "name" | "mimeType">
) {
  const credentials = await refreshGoogleWorkspaceAccessToken(admin, connection);
  const headers = {
    Authorization: `Bearer ${credentials.access_token}`,
    "User-Agent": "Mindavio-GoogleWorkspace/1.0",
  };

  if (file.mimeType.startsWith("application/vnd.google-apps.")) {
    const exportMimeType = "application/pdf";
    const response = await fetch(
      `https://www.googleapis.com/drive/v3/files/${encodeURIComponent(
        file.id
      )}/export?mimeType=${encodeURIComponent(exportMimeType)}`,
      {
        method: "GET",
        headers,
      }
    );

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(errorText || "Google Drive export failed");
    }

    return {
      buffer: Buffer.from(await response.arrayBuffer()),
      fileName: ensureFileExtension(file.name, ".pdf"),
      mimeType: exportMimeType,
    };
  }

  const response = await fetch(
    `https://www.googleapis.com/drive/v3/files/${encodeURIComponent(
      file.id
    )}?alt=media&supportsAllDrives=true`,
    {
      method: "GET",
      headers,
    }
  );

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(errorText || "Google Drive download failed");
  }

  return {
    buffer: Buffer.from(await response.arrayBuffer()),
    fileName: file.name,
    mimeType: file.mimeType || response.headers.get("content-type") || null,
  };
}
