import type { AudioRepository } from "./repositories/audio.ts";

const MAX_SEGMENT_AUDIO_BYTES = 12 * 1024 * 1024;

interface AudioBucket {
  put(key: string, value: Uint8Array, options?: { httpMetadata?: { contentType?: string } }): Promise<unknown>;
  get(key: string, options?: { range?: { offset: number; length?: number } }): Promise<{
    body: BodyInit | null;
    size: number;
    httpEtag?: string;
    range?: { offset: number; length?: number };
  } | null>;
  delete(keys: string | string[]): Promise<void>;
}

function error(status: number, code: string, message: string): Response {
  return Response.json({ error: { code, message } }, { status });
}

function safeVoice(value: string): string {
  const voice = value.trim();
  if (!/^[A-Za-z0-9_-]{2,100}$/.test(voice)) throw new Error("声音名称格式不正确。");
  return voice;
}

function isMp3(bytes: Uint8Array): boolean {
  if (bytes.length < 3) return false;
  if (bytes[0] === 0x49 && bytes[1] === 0x44 && bytes[2] === 0x33) return true;
  return bytes[0] === 0xff && (bytes[1] & 0xe0) === 0xe0;
}

function parseRange(value: string | null): { offset: number; length?: number } | null | "invalid" {
  if (!value) return null;
  if (value.includes(",")) return "invalid";
  const match = /^bytes=(\d+)-(\d*)$/i.exec(value.trim());
  if (!match) return "invalid";
  const offset = Number(match[1]);
  const end = match[2] ? Number(match[2]) : undefined;
  if (!Number.isSafeInteger(offset) || offset < 0 || (end !== undefined && (!Number.isSafeInteger(end) || end < offset))) return "invalid";
  return end === undefined ? { offset } : { offset, length: end - offset + 1 };
}

export function createAudioApi(dependencies: {
  repository: AudioRepository;
  bucket: AudioBucket;
  now?: () => Date;
}) {
  const now = dependencies.now ?? (() => new Date());

  return {
    async describe(segmentId: string, rawVoice: string): Promise<Response> {
      try {
        const voice = safeVoice(rawVoice);
        const segment = await dependencies.repository.segmentForAudio(segmentId);
        if (!segment) return error(404, "segment_not_found", "找不到可朗读的正文片段。");
        const audio = await dependencies.repository.find(segmentId, voice);
        return Response.json({ segment, voice, audio });
      } catch (cause) {
        return error(400, "invalid_audio_request", cause instanceof Error ? cause.message : "音频请求格式不正确。");
      }
    },

    async upload(request: Request, segmentId: string): Promise<Response> {
      try {
        if (request.headers.get("content-type")?.split(";", 1)[0].trim().toLowerCase() !== "audio/mpeg") {
          return error(415, "invalid_audio_type", "只接受 MP3 音频。");
        }
        const declaredSize = Number(request.headers.get("content-length") ?? 0);
        if (declaredSize > MAX_SEGMENT_AUDIO_BYTES) {
          return error(413, "audio_too_large", "单句音频超过 12 MB。");
        }
        const segment = await dependencies.repository.segmentForAudio(segmentId);
        if (!segment) return error(404, "segment_not_found", "找不到可朗读的正文片段。");
        const textVersion = Number(request.headers.get("x-text-version"));
        if (!Number.isInteger(textVersion) || textVersion !== segment.textVersion) {
          return error(409, "stale_text_version", "正文已经变化，请重新生成这一句。");
        }
        const voice = safeVoice(request.headers.get("x-voice") ?? "");
        const durationValue = request.headers.get("x-duration-ms");
        const durationMs = durationValue ? Number(durationValue) : null;
        if (durationMs !== null && (!Number.isInteger(durationMs) || durationMs < 0)) {
          return error(400, "invalid_duration", "音频时长格式不正确。");
        }
        const bytes = new Uint8Array(await request.arrayBuffer());
        if (!bytes.length) return error(400, "empty_audio", "音频不能为空。");
        if (bytes.byteLength > MAX_SEGMENT_AUDIO_BYTES) {
          return error(413, "audio_too_large", "单句音频超过 12 MB。");
        }
        if (!isMp3(bytes)) return error(415, "invalid_mp3", "音频不是有效的 MP3。");

        const cleanup = await dependencies.repository.cleanupForAdmission(segment.bookId, bytes.byteLength);
        if (!cleanup.allowed) return error(507, "storage_limit", "云端存储接近上限，请先删除书籍或音频。");
        if (cleanup.objectKeys.length) await dependencies.bucket.delete(cleanup.objectKeys);
        const objectKey = `audio/${segment.bookId}/${segment.id}/v${segment.textVersion}/${voice}.mp3`;
        await dependencies.bucket.put(objectKey, bytes, { httpMetadata: { contentType: "audio/mpeg" } });
        let audio;
        try {
          audio = await dependencies.repository.registerReady({
            segmentId,
            textVersion,
            voice,
            objectKey,
            bytes: bytes.byteLength,
            durationMs,
            now: now().toISOString(),
          });
        } catch (cause) {
          await dependencies.bucket.delete(objectKey);
          throw cause;
        }
        return Response.json({ audio, evictedBookIds: cleanup.evictedBookIds }, { status: 201 });
      } catch (cause) {
        return error(400, "audio_upload_failed", cause instanceof Error ? cause.message : "音频上传失败。");
      }
    },

    async stream(request: Request, segmentId: string, rawVoice: string): Promise<Response> {
      try {
        const voice = safeVoice(rawVoice);
        const range = parseRange(request.headers.get("range"));
        if (range === "invalid") {
          return new Response(null, { status: 416, headers: { "accept-ranges": "bytes" } });
        }
        const result = await dependencies.repository.objectForAudio(segmentId, voice);
        if (!result) return error(404, "audio_not_found", "这句话还没有生成音频。");
        const object = await dependencies.bucket.get(result.objectKey, range ? { range } : undefined);
        if (!object) return error(404, "audio_not_found", "音频文件已经失效，请重新生成。");
        const partial = Boolean(range && object.range);
        const contentLength = partial && object.range ? object.range.length ?? Math.max(0, object.size - object.range.offset) : object.size;
        const headers: Record<string, string> = {
          "content-type": "audio/mpeg",
          "content-length": String(contentLength),
          "accept-ranges": "bytes",
          "cache-control": "private, no-store",
          etag: object.httpEtag ?? "",
        };
        if (partial && object.range) {
          headers["content-range"] = `bytes ${object.range.offset}-${object.range.offset + contentLength - 1}/${object.size}`;
        }
        return new Response(object.body, {
          status: partial ? 206 : 200,
          headers,
        });
      } catch (cause) {
        return error(400, "invalid_audio_request", cause instanceof Error ? cause.message : "音频请求格式不正确。");
      }
    },

    async listChapter(bookId: string, chapterId: string): Promise<Response> {
      try {
        return Response.json({ audio: await dependencies.repository.listChapter(bookId, chapterId) });
      } catch (cause) {
        return error(400, "chapter_audio_failed", cause instanceof Error ? cause.message : "章节音频无法加载。");
      }
    },
  };
}
