import { aiGenerationClient } from "../../api/aiGenerationClient";
import type { WebCanvasWorkflowAssetRef } from "../../types";

interface CanvasGeneratedResultInput {
  url: string;
  taskId: string;
  mediaType: string;
  resultType?: "image" | "video";
  ossKey?: string | null;
  originalUrl?: string | null;
  expiresAt?: string | null;
}

interface PersistCanvasGeneratedResultInput extends CanvasGeneratedResultInput {
  title: string;
  client?: CanvasAssetPersistenceClient;
}

interface CanvasAssetPersistenceClient {
  downloadTaskResult(taskId: string): Promise<{ blob: Blob; filename?: string; contentType?: string }>;
  uploadAsset(input: {
    dataUrl: string;
    name?: string;
    mimeType?: string;
    scope?: string;
  }): Promise<{ url: string; signedUrl?: string; ossKey?: string }>;
  uploadAssetByUrl(input: {
    sourceUrl: string;
    name?: string;
    mimeType?: string;
    scope?: string;
  }): Promise<{ url: string; signedUrl?: string; ossKey?: string }>;
  fetchAsset?: (url: string) => Promise<{
    ok: boolean;
    status: number;
    headers: { get(name: string): string | null };
    blob(): Promise<Blob>;
  }>;
}

function getDefaultClient(): CanvasAssetPersistenceClient {
  return {
    downloadTaskResult: (taskId) => aiGenerationClient.downloadTaskResult(taskId),
    uploadAsset: (input) => aiGenerationClient.uploadAsset(input),
    uploadAssetByUrl: (input) => aiGenerationClient.uploadAssetByUrl(input),
    fetchAsset: (url) => fetch(url, { credentials: "omit" }),
  };
}

function getGeneratedResultOssKey(url: string): string | null {
  try {
    const key = decodeURIComponent(new URL(url, globalThis.location?.href || "http://localhost").pathname.replace(/^\/+/, ""));
    return /^users\/[^/]+\/generation-results\/.+/i.test(key) ? key : null;
  } catch {
    return null;
  }
}

export function createCanvasAssetRefFromGeneratedResult(
  input: CanvasGeneratedResultInput,
): WebCanvasWorkflowAssetRef {
  const inferredOssKey = input.ossKey || getGeneratedResultOssKey(input.url);
  const mediaType = input.mediaType || (input.resultType === "video" ? "video/mp4" : "image/png");
  return {
    url: input.url,
    mediaType,
    sourceTaskId: input.taskId,
    ossKey: inferredOssKey || null,
    originalUrl: input.originalUrl || null,
    expiresAt: input.expiresAt || null,
  };
}

function getCanvasResultExtension(mediaType: string, fallbackUrl: string) {
  const mime = mediaType.split(";")[0]?.trim().toLowerCase();
  if (mime === "image/jpeg") return "jpg";
  if (mime === "image/png") return "png";
  if (mime === "image/webp") return "webp";
  if (mime === "image/gif") return "gif";
  if (mime === "video/mp4") return "mp4";
  if (mime === "video/webm") return "webm";

  try {
    const matched = new URL(fallbackUrl, globalThis.location?.href || "http://localhost")
      .pathname
      .match(/\.([a-z0-9]{2,5})$/i);
    if (matched?.[1]) return matched[1].toLowerCase();
  } catch {
    // Keep media fallback below.
  }

  return mediaType.startsWith("video/") ? "mp4" : "png";
}

function buildCanvasResultFileName(title: string, mediaType: string, fallbackUrl: string) {
  const normalized = title
    .trim()
    .replace(/[\\/:*?"<>|]+/g, "-")
    .replace(/\s+/g, " ")
    .slice(0, 80)
    .trim();
  return `${normalized || "canvas-result"}.${getCanvasResultExtension(mediaType, fallbackUrl)}`;
}

function blobToDataUrl(blob: Blob): Promise<string> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      if (typeof reader.result === "string") {
        resolve(reader.result);
      } else {
        reject(new Error("Unable to read canvas result"));
      }
    };
    reader.onerror = () => reject(reader.error || new Error("Unable to read canvas result"));
    reader.readAsDataURL(blob);
  });
}

async function downloadCanvasResultBlob(
  input: PersistCanvasGeneratedResultInput,
  client: CanvasAssetPersistenceClient,
) {
  try {
    return await client.downloadTaskResult(input.taskId);
  } catch {
    const fetchAsset = client.fetchAsset || ((url: string) => fetch(url, { credentials: "omit" }));
    const response = await fetchAsset(input.url);
    if (!response.ok) throw new Error(`Canvas result request failed: ${response.status}`);
    const blob = await response.blob();
    return {
      blob,
      contentType: blob.type || response.headers.get("content-type") || undefined,
    };
  }
}

export async function persistCanvasGeneratedResultAsset(
  input: PersistCanvasGeneratedResultInput,
): Promise<WebCanvasWorkflowAssetRef> {
  const fallbackRef = createCanvasAssetRefFromGeneratedResult(input);
  if (fallbackRef.ossKey) return fallbackRef;

  const client = input.client || getDefaultClient();
  const fallbackMediaType = input.mediaType || (input.resultType === "video" ? "video/mp4" : "image/png");
  const name = buildCanvasResultFileName(input.title, fallbackMediaType, input.url);

  // Try server-side URL-to-OSS first
  try {
    const uploaded = await client.uploadAssetByUrl({
      sourceUrl: input.url,
      name,
      mimeType: fallbackMediaType,
      scope: "canvas-node-result",
    });
    return createCanvasAssetRefFromGeneratedResult({
      ...input,
      url: uploaded.url,
      ossKey: uploaded.ossKey || null,
      mediaType: fallbackMediaType,
      originalUrl: input.originalUrl || input.url,
    });
  } catch (urlError) {
    console.warn("[canvas] URL upload failed, falling back to download+base64:", urlError instanceof Error ? urlError.message : urlError);
  }

  // Fallback: download → base64 → upload
  try {
    const downloaded = await downloadCanvasResultBlob(input, client);
    const mimeType = downloaded.contentType || downloaded.blob.type || fallbackMediaType;
    const dataUrl = await blobToDataUrl(downloaded.blob);
    const uploaded = await client.uploadAsset({
      dataUrl,
      name: downloaded.filename || name,
      mimeType,
      scope: "canvas-node-result",
    });

    return createCanvasAssetRefFromGeneratedResult({
      ...input,
      url: uploaded.url,
      ossKey: uploaded.ossKey || null,
      mediaType: mimeType,
      originalUrl: input.originalUrl || input.url,
    });
  } catch (error) {
    const msg = error instanceof Error ? error.message : String(error || "");
    if (/413|too large/i.test(msg)) {
      console.warn("[canvas] OSS upload rejected (file too large), using temporary URL:", msg);
    } else {
      console.warn("[canvas] result persistence fallback:", error);
    }
    return fallbackRef;
  }
}
