/**
 * Speech Configuration Tests
 *
 * Issue #401: Tests for speech services environment variable validation
 * Tests cover STT, TTS (default, premium, fallback), and speech limits configuration.
 */

import { describe, it, expect, beforeEach, afterEach } from "vitest";
import {
  isSttEnabled,
  isTtsEnabled,
  isTtsPremiumEnabled,
  isTtsFallbackEnabled,
  validateSpeechConfig,
  getSpeechConfig,
  type SpeechConfig,
} from "./speech.config";

describe("speech.config", () => {
  const originalEnv = { ...process.env };

  beforeEach(() => {
    // Clear all speech-related env vars before each test
    delete process.env.STT_ENABLED;
    delete process.env.STT_BASE_URL;
    delete process.env.STT_MODEL;
    delete process.env.STT_LANGUAGE;
    delete process.env.TTS_ENABLED;
    delete process.env.TTS_DEFAULT_URL;
    delete process.env.TTS_DEFAULT_VOICE;
    delete process.env.TTS_DEFAULT_FORMAT;
    delete process.env.TTS_PREMIUM_ENABLED;
    delete process.env.TTS_PREMIUM_URL;
    delete process.env.TTS_FALLBACK_ENABLED;
    delete process.env.TTS_FALLBACK_URL;
    delete process.env.SPEECH_MAX_UPLOAD_SIZE;
    delete process.env.SPEECH_MAX_DURATION_SECONDS;
    delete process.env.SPEECH_MAX_TEXT_LENGTH;
  });

  afterEach(() => {
    process.env = { ...originalEnv };
  });

  // ==========================================
  // STT enabled check
  // ==========================================
  describe("isSttEnabled", () => {
    it("should return false when STT_ENABLED is not set", () => {
      expect(isSttEnabled()).toBe(false);
    });

    it("should return false when STT_ENABLED is 'false'", () => {
      process.env.STT_ENABLED = "false";
      expect(isSttEnabled()).toBe(false);
    });

    it("should return false when STT_ENABLED is '0'", () => {
      process.env.STT_ENABLED = "0";
      expect(isSttEnabled()).toBe(false);
    });

    it("should return false when STT_ENABLED is empty string", () => {
      process.env.STT_ENABLED = "";
      expect(isSttEnabled()).toBe(false);
    });

    it("should return true when STT_ENABLED is 'true'", () => {
      process.env.STT_ENABLED = "true";
      expect(isSttEnabled()).toBe(true);
    });

    it("should return true when STT_ENABLED is '1'", () => {
      process.env.STT_ENABLED = "1";
      expect(isSttEnabled()).toBe(true);
    });
  });

  // ==========================================
  // TTS enabled check
  // ==========================================
  describe("isTtsEnabled", () => {
    it("should return false when TTS_ENABLED is not set", () => {
      expect(isTtsEnabled()).toBe(false);
    });

    it("should return false when TTS_ENABLED is 'false'", () => {
      process.env.TTS_ENABLED = "false";
      expect(isTtsEnabled()).toBe(false);
    });

    it("should return true when TTS_ENABLED is 'true'", () => {
      process.env.TTS_ENABLED = "true";
      expect(isTtsEnabled()).toBe(true);
    });

    it("should return true when TTS_ENABLED is '1'", () => {
      process.env.TTS_ENABLED = "1";
      expect(isTtsEnabled()).toBe(true);
    });
  });

  // ==========================================
  // TTS premium enabled check
  // ==========================================
  describe("isTtsPremiumEnabled", () => {
    it("should return false when TTS_PREMIUM_ENABLED is not set", () => {
      expect(isTtsPremiumEnabled()).toBe(false);
    });

    it("should return false when TTS_PREMIUM_ENABLED is 'false'", () => {
      process.env.TTS_PREMIUM_ENABLED = "false";
      expect(isTtsPremiumEnabled()).toBe(false);
    });

    it("should return true when TTS_PREMIUM_ENABLED is 'true'", () => {
      process.env.TTS_PREMIUM_ENABLED = "true";
      expect(isTtsPremiumEnabled()).toBe(true);
    });
  });

  // ==========================================
  // TTS fallback enabled check
  // ==========================================
  describe("isTtsFallbackEnabled", () => {
    it("should return false when TTS_FALLBACK_ENABLED is not set", () => {
      expect(isTtsFallbackEnabled()).toBe(false);
    });

    it("should return false when TTS_FALLBACK_ENABLED is 'false'", () => {
      process.env.TTS_FALLBACK_ENABLED = "false";
      expect(isTtsFallbackEnabled()).toBe(false);
    });

    it("should return true when TTS_FALLBACK_ENABLED is 'true'", () => {
      process.env.TTS_FALLBACK_ENABLED = "true";
      expect(isTtsFallbackEnabled()).toBe(true);
    });
  });

  // ==========================================
  // validateSpeechConfig
  // ==========================================
  describe("validateSpeechConfig", () => {
    describe("when all services are disabled", () => {
      it("should not throw when no speech services are enabled", () => {
        expect(() => validateSpeechConfig()).not.toThrow();
      });

      it("should not throw when services are explicitly disabled", () => {
        process.env.STT_ENABLED = "false";
        process.env.TTS_ENABLED = "false";
        process.env.TTS_PREMIUM_ENABLED = "false";
        process.env.TTS_FALLBACK_ENABLED = "false";
        expect(() => validateSpeechConfig()).not.toThrow();
      });
    });

    describe("STT validation", () => {
      beforeEach(() => {
        process.env.STT_ENABLED = "true";
      });

      it("should throw when STT is enabled but STT_BASE_URL is missing", () => {
        expect(() => validateSpeechConfig()).toThrow("STT_BASE_URL");
        expect(() => validateSpeechConfig()).toThrow(
          "STT is enabled (STT_ENABLED=true) but required environment variables are missing"
        );
      });

      it("should throw when STT_BASE_URL is empty string", () => {
        process.env.STT_BASE_URL = "";
        expect(() => validateSpeechConfig()).toThrow("STT_BASE_URL");
      });

      it("should throw when STT_BASE_URL is whitespace only", () => {
        process.env.STT_BASE_URL = "   ";
        expect(() => validateSpeechConfig()).toThrow("STT_BASE_URL");
      });

      it("should not throw when STT is enabled and STT_BASE_URL is set", () => {
        process.env.STT_BASE_URL = "http://speaches:8000/v1";
        expect(() => validateSpeechConfig()).not.toThrow();
      });

      it("should suggest disabling STT in error message", () => {
        expect(() => validateSpeechConfig()).toThrow("STT_ENABLED=false");
      });
    });

    describe("TTS default validation", () => {
      beforeEach(() => {
        process.env.TTS_ENABLED = "true";
      });

      it("should throw when TTS is enabled but TTS_DEFAULT_URL is missing", () => {
        expect(() => validateSpeechConfig()).toThrow("TTS_DEFAULT_URL");
        expect(() => validateSpeechConfig()).toThrow(
          "TTS is enabled (TTS_ENABLED=true) but required environment variables are missing"
        );
      });

      it("should throw when TTS_DEFAULT_URL is empty string", () => {
        process.env.TTS_DEFAULT_URL = "";
        expect(() => validateSpeechConfig()).toThrow("TTS_DEFAULT_URL");
      });

      it("should not throw when TTS is enabled and TTS_DEFAULT_URL is set", () => {
        process.env.TTS_DEFAULT_URL = "http://kokoro-tts:8880/v1";
        expect(() => validateSpeechConfig()).not.toThrow();
      });

      it("should suggest disabling TTS in error message", () => {
        expect(() => validateSpeechConfig()).toThrow("TTS_ENABLED=false");
      });
    });

    describe("TTS premium validation", () => {
      beforeEach(() => {
        process.env.TTS_PREMIUM_ENABLED = "true";
      });

      it("should throw when TTS premium is enabled but TTS_PREMIUM_URL is missing", () => {
        expect(() => validateSpeechConfig()).toThrow("TTS_PREMIUM_URL");
        expect(() => validateSpeechConfig()).toThrow(
          "TTS premium is enabled (TTS_PREMIUM_ENABLED=true) but required environment variables are missing"
        );
      });

      it("should throw when TTS_PREMIUM_URL is empty string", () => {
        process.env.TTS_PREMIUM_URL = "";
        expect(() => validateSpeechConfig()).toThrow("TTS_PREMIUM_URL");
      });

      it("should not throw when TTS premium is enabled and TTS_PREMIUM_URL is set", () => {
        process.env.TTS_PREMIUM_URL = "http://chatterbox-tts:8881/v1";
        expect(() => validateSpeechConfig()).not.toThrow();
      });

      it("should suggest disabling TTS premium in error message", () => {
        expect(() => validateSpeechConfig()).toThrow("TTS_PREMIUM_ENABLED=false");
      });
    });

    describe("TTS fallback validation", () => {
      beforeEach(() => {
        process.env.TTS_FALLBACK_ENABLED = "true";
      });

      it("should throw when TTS fallback is enabled but TTS_FALLBACK_URL is missing", () => {
        expect(() => validateSpeechConfig()).toThrow("TTS_FALLBACK_URL");
        expect(() => validateSpeechConfig()).toThrow(
          "TTS fallback is enabled (TTS_FALLBACK_ENABLED=true) but required environment variables are missing"
        );
      });

      it("should throw when TTS_FALLBACK_URL is empty string", () => {
        process.env.TTS_FALLBACK_URL = "";
        expect(() => validateSpeechConfig()).toThrow("TTS_FALLBACK_URL");
      });

      it("should not throw when TTS fallback is enabled and TTS_FALLBACK_URL is set", () => {
        process.env.TTS_FALLBACK_URL = "http://openedai-speech:8000/v1";
        expect(() => validateSpeechConfig()).not.toThrow();
      });

      it("should suggest disabling TTS fallback in error message", () => {
        expect(() => validateSpeechConfig()).toThrow("TTS_FALLBACK_ENABLED=false");
      });
    });

    describe("multiple services enabled simultaneously", () => {
      it("should validate all enabled services", () => {
        process.env.STT_ENABLED = "true";
        process.env.TTS_ENABLED = "true";
        // Missing both STT_BASE_URL and TTS_DEFAULT_URL

        expect(() => validateSpeechConfig()).toThrow("STT_BASE_URL");
      });

      it("should pass when all enabled services are properly configured", () => {
        process.env.STT_ENABLED = "true";
        process.env.STT_BASE_URL = "http://speaches:8000/v1";
        process.env.TTS_ENABLED = "true";
        process.env.TTS_DEFAULT_URL = "http://kokoro-tts:8880/v1";
        process.env.TTS_PREMIUM_ENABLED = "true";
        process.env.TTS_PREMIUM_URL = "http://chatterbox-tts:8881/v1";
        process.env.TTS_FALLBACK_ENABLED = "true";
        process.env.TTS_FALLBACK_URL = "http://openedai-speech:8000/v1";

        expect(() => validateSpeechConfig()).not.toThrow();
      });
    });

    describe("limits validation", () => {
      it("should throw when SPEECH_MAX_UPLOAD_SIZE is not a valid number", () => {
        process.env.SPEECH_MAX_UPLOAD_SIZE = "not-a-number";
        expect(() => validateSpeechConfig()).toThrow("SPEECH_MAX_UPLOAD_SIZE");
        expect(() => validateSpeechConfig()).toThrow("must be a positive integer");
      });

      it("should throw when SPEECH_MAX_UPLOAD_SIZE is negative", () => {
        process.env.SPEECH_MAX_UPLOAD_SIZE = "-100";
        expect(() => validateSpeechConfig()).toThrow("SPEECH_MAX_UPLOAD_SIZE");
      });

      it("should throw when SPEECH_MAX_UPLOAD_SIZE is zero", () => {
        process.env.SPEECH_MAX_UPLOAD_SIZE = "0";
        expect(() => validateSpeechConfig()).toThrow("SPEECH_MAX_UPLOAD_SIZE");
      });

      it("should throw when SPEECH_MAX_DURATION_SECONDS is not a valid number", () => {
        process.env.SPEECH_MAX_DURATION_SECONDS = "abc";
        expect(() => validateSpeechConfig()).toThrow("SPEECH_MAX_DURATION_SECONDS");
      });

      it("should throw when SPEECH_MAX_TEXT_LENGTH is not a valid number", () => {
        process.env.SPEECH_MAX_TEXT_LENGTH = "xyz";
        expect(() => validateSpeechConfig()).toThrow("SPEECH_MAX_TEXT_LENGTH");
      });

      it("should not throw when limits are valid positive integers", () => {
        process.env.SPEECH_MAX_UPLOAD_SIZE = "50000000";
        process.env.SPEECH_MAX_DURATION_SECONDS = "1200";
        process.env.SPEECH_MAX_TEXT_LENGTH = "8192";
        expect(() => validateSpeechConfig()).not.toThrow();
      });

      it("should not throw when limits are not set (uses defaults)", () => {
        expect(() => validateSpeechConfig()).not.toThrow();
      });
    });
  });

  // ==========================================
  // getSpeechConfig
  // ==========================================
  describe("getSpeechConfig", () => {
    it("should return default values when no env vars are set", () => {
      const config = getSpeechConfig();

      expect(config.stt.enabled).toBe(false);
      expect(config.stt.baseUrl).toBe("http://speaches:8000/v1");
      expect(config.stt.model).toBe("Systran/faster-whisper-large-v3-turbo");
      expect(config.stt.language).toBe("en");

      expect(config.tts.default.enabled).toBe(false);
      expect(config.tts.default.url).toBe("http://kokoro-tts:8880/v1");
      expect(config.tts.default.voice).toBe("af_heart");
      expect(config.tts.default.format).toBe("mp3");

      expect(config.tts.premium.enabled).toBe(false);
      expect(config.tts.premium.url).toBe("http://chatterbox-tts:8881/v1");

      expect(config.tts.fallback.enabled).toBe(false);
      expect(config.tts.fallback.url).toBe("http://openedai-speech:8000/v1");

      expect(config.limits.maxUploadSize).toBe(25000000);
      expect(config.limits.maxDurationSeconds).toBe(600);
      expect(config.limits.maxTextLength).toBe(4096);
    });

    it("should use custom env var values when set", () => {
      process.env.STT_ENABLED = "true";
      process.env.STT_BASE_URL = "http://custom-stt:9000/v1";
      process.env.STT_MODEL = "custom-model";
      process.env.STT_LANGUAGE = "fr";

      process.env.TTS_ENABLED = "true";
      process.env.TTS_DEFAULT_URL = "http://custom-tts:9001/v1";
      process.env.TTS_DEFAULT_VOICE = "custom_voice";
      process.env.TTS_DEFAULT_FORMAT = "wav";

      process.env.TTS_PREMIUM_ENABLED = "true";
      process.env.TTS_PREMIUM_URL = "http://custom-premium:9002/v1";

      process.env.TTS_FALLBACK_ENABLED = "true";
      process.env.TTS_FALLBACK_URL = "http://custom-fallback:9003/v1";

      process.env.SPEECH_MAX_UPLOAD_SIZE = "50000000";
      process.env.SPEECH_MAX_DURATION_SECONDS = "1200";
      process.env.SPEECH_MAX_TEXT_LENGTH = "8192";

      const config = getSpeechConfig();

      expect(config.stt.enabled).toBe(true);
      expect(config.stt.baseUrl).toBe("http://custom-stt:9000/v1");
      expect(config.stt.model).toBe("custom-model");
      expect(config.stt.language).toBe("fr");

      expect(config.tts.default.enabled).toBe(true);
      expect(config.tts.default.url).toBe("http://custom-tts:9001/v1");
      expect(config.tts.default.voice).toBe("custom_voice");
      expect(config.tts.default.format).toBe("wav");

      expect(config.tts.premium.enabled).toBe(true);
      expect(config.tts.premium.url).toBe("http://custom-premium:9002/v1");

      expect(config.tts.fallback.enabled).toBe(true);
      expect(config.tts.fallback.url).toBe("http://custom-fallback:9003/v1");

      expect(config.limits.maxUploadSize).toBe(50000000);
      expect(config.limits.maxDurationSeconds).toBe(1200);
      expect(config.limits.maxTextLength).toBe(8192);
    });

    it("should return typed SpeechConfig object", () => {
      const config: SpeechConfig = getSpeechConfig();

      // Verify structure matches the SpeechConfig type
      expect(config).toHaveProperty("stt");
      expect(config).toHaveProperty("tts");
      expect(config).toHaveProperty("limits");
      expect(config.tts).toHaveProperty("default");
      expect(config.tts).toHaveProperty("premium");
      expect(config.tts).toHaveProperty("fallback");
    });

    it("should handle partial env var overrides", () => {
      process.env.STT_ENABLED = "true";
      process.env.STT_BASE_URL = "http://custom-stt:9000/v1";
      // STT_MODEL and STT_LANGUAGE not set, should use defaults

      const config = getSpeechConfig();

      expect(config.stt.enabled).toBe(true);
      expect(config.stt.baseUrl).toBe("http://custom-stt:9000/v1");
      expect(config.stt.model).toBe("Systran/faster-whisper-large-v3-turbo");
      expect(config.stt.language).toBe("en");
    });

    it("should parse numeric limits correctly", () => {
      process.env.SPEECH_MAX_UPLOAD_SIZE = "10000000";
      const config = getSpeechConfig();
      expect(typeof config.limits.maxUploadSize).toBe("number");
      expect(config.limits.maxUploadSize).toBe(10000000);
    });
  });

  // ==========================================
  // registerAs integration
  // ==========================================
  describe("speechConfig (registerAs factory)", () => {
    it("should be importable as a config namespace factory", async () => {
      const { speechConfig } = await import("./speech.config");
      expect(speechConfig).toBeDefined();
      expect(speechConfig.KEY).toBe("CONFIGURATION(speech)");
    });

    it("should return config object when called", async () => {
      const { speechConfig } = await import("./speech.config");
      const config = speechConfig() as SpeechConfig;
      expect(config).toHaveProperty("stt");
      expect(config).toHaveProperty("tts");
      expect(config).toHaveProperty("limits");
    });
  });
});
