/**
 * KokoroTtsProvider Unit Tests
 *
 * Tests the Kokoro-FastAPI TTS provider with full voice catalog,
 * voice metadata parsing, and Kokoro-specific feature constants.
 *
 * Issue #393
 */

import { describe, it, expect, vi, beforeEach } from "vitest";
import {
  KokoroTtsProvider,
  KOKORO_SUPPORTED_FORMATS,
  KOKORO_SPEED_RANGE,
  KOKORO_VOICES,
  parseVoicePrefix,
} from "./kokoro-tts.provider";
import type { VoiceInfo } from "../interfaces/speech-types";

// ==========================================
// Mock OpenAI SDK
// ==========================================

vi.mock("openai", () => {
  class MockOpenAI {
    audio = {
      speech: {
        create: vi.fn(),
      },
    };
  }
  return { default: MockOpenAI };
});

// ==========================================
// Provider identity
// ==========================================

describe("KokoroTtsProvider", () => {
  const testBaseURL = "http://kokoro-tts:8880/v1";
  let provider: KokoroTtsProvider;

  beforeEach(() => {
    provider = new KokoroTtsProvider(testBaseURL);
  });

  describe("provider identity", () => {
    it("should have name 'kokoro'", () => {
      expect(provider.name).toBe("kokoro");
    });

    it("should have tier 'default'", () => {
      expect(provider.tier).toBe("default");
    });
  });

  // ==========================================
  // listVoices()
  // ==========================================

  describe("listVoices", () => {
    let voices: VoiceInfo[];

    beforeEach(async () => {
      voices = await provider.listVoices();
    });

    it("should return an array of VoiceInfo objects", () => {
      expect(voices).toBeInstanceOf(Array);
      expect(voices.length).toBeGreaterThan(0);
    });

    it("should return at least 10 voices", () => {
      // The issue specifies at least: af_heart, af_bella, af_nicole, af_sarah, af_sky,
      // am_adam, am_michael, bf_emma, bf_isabella, bm_george, bm_lewis
      expect(voices.length).toBeGreaterThanOrEqual(10);
    });

    it("should set tier to 'default' on all voices", () => {
      for (const voice of voices) {
        expect(voice.tier).toBe("default");
      }
    });

    it("should have exactly one default voice", () => {
      const defaults = voices.filter((v) => v.isDefault === true);
      expect(defaults.length).toBe(1);
    });

    it("should mark af_heart as the default voice", () => {
      const defaultVoice = voices.find((v) => v.isDefault === true);
      expect(defaultVoice).toBeDefined();
      expect(defaultVoice?.id).toBe("af_heart");
    });

    it("should have an id and name for every voice", () => {
      for (const voice of voices) {
        expect(voice.id).toBeTruthy();
        expect(voice.name).toBeTruthy();
      }
    });

    it("should set language on every voice", () => {
      for (const voice of voices) {
        expect(voice.language).toBeTruthy();
      }
    });

    // ==========================================
    // Required voices from the issue
    // ==========================================

    describe("required voices", () => {
      const requiredVoiceIds = [
        "af_heart",
        "af_bella",
        "af_nicole",
        "af_sarah",
        "af_sky",
        "am_adam",
        "am_michael",
        "bf_emma",
        "bf_isabella",
        "bm_george",
        "bm_lewis",
      ];

      it.each(requiredVoiceIds)("should include voice '%s'", (voiceId) => {
        const voice = voices.find((v) => v.id === voiceId);
        expect(voice).toBeDefined();
      });
    });

    // ==========================================
    // Voice metadata from prefix
    // ==========================================

    describe("voice metadata from prefix", () => {
      it("should set language to 'en-US' for af_ prefix voices", () => {
        const voice = voices.find((v) => v.id === "af_heart");
        expect(voice?.language).toBe("en-US");
      });

      it("should set language to 'en-US' for am_ prefix voices", () => {
        const voice = voices.find((v) => v.id === "am_adam");
        expect(voice?.language).toBe("en-US");
      });

      it("should set language to 'en-GB' for bf_ prefix voices", () => {
        const voice = voices.find((v) => v.id === "bf_emma");
        expect(voice?.language).toBe("en-GB");
      });

      it("should set language to 'en-GB' for bm_ prefix voices", () => {
        const voice = voices.find((v) => v.id === "bm_george");
        expect(voice?.language).toBe("en-GB");
      });

      it("should include gender in voice name for af_ prefix", () => {
        const voice = voices.find((v) => v.id === "af_heart");
        expect(voice?.name).toContain("Female");
      });

      it("should include gender in voice name for am_ prefix", () => {
        const voice = voices.find((v) => v.id === "am_adam");
        expect(voice?.name).toContain("Male");
      });

      it("should include gender in voice name for bf_ prefix", () => {
        const voice = voices.find((v) => v.id === "bf_emma");
        expect(voice?.name).toContain("Female");
      });

      it("should include gender in voice name for bm_ prefix", () => {
        const voice = voices.find((v) => v.id === "bm_george");
        expect(voice?.name).toContain("Male");
      });
    });

    // ==========================================
    // Voice name formatting
    // ==========================================

    describe("voice name formatting", () => {
      it("should capitalize the voice name portion", () => {
        const voice = voices.find((v) => v.id === "af_heart");
        expect(voice?.name).toContain("Heart");
      });

      it("should include the accent/language label in the name", () => {
        const afVoice = voices.find((v) => v.id === "af_heart");
        expect(afVoice?.name).toContain("American");

        const bfVoice = voices.find((v) => v.id === "bf_emma");
        expect(bfVoice?.name).toContain("British");
      });
    });
  });

  // ==========================================
  // Custom constructor
  // ==========================================

  describe("constructor", () => {
    it("should accept custom default voice", () => {
      const customProvider = new KokoroTtsProvider(testBaseURL, "af_bella");
      expect(customProvider).toBeDefined();
    });

    it("should accept custom default format", () => {
      const customProvider = new KokoroTtsProvider(testBaseURL, "af_heart", "wav");
      expect(customProvider).toBeDefined();
    });

    it("should use af_heart as default voice when none specified", () => {
      const defaultProvider = new KokoroTtsProvider(testBaseURL);
      expect(defaultProvider).toBeDefined();
    });
  });
});

// ==========================================
// parseVoicePrefix utility
// ==========================================

describe("parseVoicePrefix", () => {
  it("should parse af_ as American English Female", () => {
    const result = parseVoicePrefix("af_heart");
    expect(result.language).toBe("en-US");
    expect(result.gender).toBe("female");
    expect(result.accent).toBe("American");
  });

  it("should parse am_ as American English Male", () => {
    const result = parseVoicePrefix("am_adam");
    expect(result.language).toBe("en-US");
    expect(result.gender).toBe("male");
    expect(result.accent).toBe("American");
  });

  it("should parse bf_ as British English Female", () => {
    const result = parseVoicePrefix("bf_emma");
    expect(result.language).toBe("en-GB");
    expect(result.gender).toBe("female");
    expect(result.accent).toBe("British");
  });

  it("should parse bm_ as British English Male", () => {
    const result = parseVoicePrefix("bm_george");
    expect(result.language).toBe("en-GB");
    expect(result.gender).toBe("male");
    expect(result.accent).toBe("British");
  });

  it("should return unknown for unrecognized prefix", () => {
    const result = parseVoicePrefix("xx_unknown");
    expect(result.language).toBe("unknown");
    expect(result.gender).toBe("unknown");
    expect(result.accent).toBe("Unknown");
  });
});

// ==========================================
// Exported constants
// ==========================================

describe("KOKORO_SUPPORTED_FORMATS", () => {
  it("should include mp3", () => {
    expect(KOKORO_SUPPORTED_FORMATS).toContain("mp3");
  });

  it("should include wav", () => {
    expect(KOKORO_SUPPORTED_FORMATS).toContain("wav");
  });

  it("should include opus", () => {
    expect(KOKORO_SUPPORTED_FORMATS).toContain("opus");
  });

  it("should include flac", () => {
    expect(KOKORO_SUPPORTED_FORMATS).toContain("flac");
  });

  it("should be a readonly array", () => {
    expect(Array.isArray(KOKORO_SUPPORTED_FORMATS)).toBe(true);
  });
});

describe("KOKORO_SPEED_RANGE", () => {
  it("should have min speed of 0.25", () => {
    expect(KOKORO_SPEED_RANGE.min).toBe(0.25);
  });

  it("should have max speed of 4.0", () => {
    expect(KOKORO_SPEED_RANGE.max).toBe(4.0);
  });
});

describe("KOKORO_VOICES", () => {
  it("should be a non-empty array", () => {
    expect(Array.isArray(KOKORO_VOICES)).toBe(true);
    expect(KOKORO_VOICES.length).toBeGreaterThan(0);
  });

  it("should contain voice entries with id and label", () => {
    for (const voice of KOKORO_VOICES) {
      expect(voice.id).toBeTruthy();
      expect(voice.label).toBeTruthy();
    }
  });

  it("should include voices from multiple language prefixes", () => {
    const prefixes = new Set(KOKORO_VOICES.map((v) => v.id.substring(0, 2)));
    expect(prefixes.size).toBeGreaterThanOrEqual(4);
  });
});
