import { hostname } from "os";
import config, { COLOR, envVar } from "../src/config";

describe("envVar", () => {
  const OLD_ENV = process.env;

  beforeEach(() => {
    jest.resetModules();
    process.env = { ...OLD_ENV };

    process.env["bnb-var"] = "bnb-var-value";
    process.env["bnb-legacy2"] = "bnb-legacy2-value";
    process.env["bnb-legacy3"] = "bnb-legacy3-value";
  });

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

  describe("when the env var exists", () => {
    describe("and there are no legacy env vars that match", () => {
      it("should return the env var", () => {
        expect(envVar("bnb-var")).toEqual("bnb-var-value");
      });
    });

    describe("and there are legacy env vars that match", () => {
      it("should return the env var", () => {
        expect(
          envVar("bnb-var", {
            default: "not valid",
            legacy: ["bnb-legacy1", "bnb-legacy2", "bnb-legacy3"],
          })
        ).toEqual("bnb-var-value");
      });
    });
  });

  describe("when the env var doesnt exist", () => {
    describe("and there are no legacy env vars specified", () => {
      describe("and there is no default value specified", () => {
        it("should be undefined", () => {
          expect(envVar("bnb-not-set")).toBeUndefined();
        });
      });

      describe("and there is a default value specified", () => {
        it("should return the default", () => {
          expect(envVar("bnb-not-set", { default: "widget" })).toEqual(
            "widget"
          );
        });
      });
    });

    describe("when there are legacy env vars specified", () => {
      it("should return the value from the first matched legacy env var", () => {
        expect(
          envVar("bnb-not-set", {
            legacy: ["bnb-legacy1", "bnb-legacy2", "bnb-legacy3"],
          })
        ).toEqual("bnb-legacy2-value");
      });
    });
  });

  describe("validationPattern", () => {
    it("should fail when the value does not match the pattern", () => {
      expect(() =>
        envVar("bnb-var", {
          validationPattern: /^foobar$/,
        })
      ).toThrowError(
        `Invalid value specified for 'bnb-var', must match ${/^foobar$/}`
      );
    });
  });
});

describe("config", () => {
  const OLD_ENV = process.env;

  beforeEach(() => {
    jest.resetModules();
    process.env = { ...OLD_ENV };
  });

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

  function describeBooleanConfigValue(
    name: string,
    envVar: string,
    expectedDefault: boolean,
    propertyGetter: (config: any) => any
  ) {
    describe(name, () => {
      it.each([
        [expectedDefault, ""],
        [expectedDefault, undefined],
        [true, "true"],
        [false, "false"],
        [false, "foo"],
      ])("should be %s when env var is '%s'", (expected, value) => {
        process.env[envVar] = value;
        expect(propertyGetter(config())).toEqual(expected);
      })
    });
  }

  describe("bonobUrl", () => {
      describe.each([
        "BNB_URL", 
        "BONOB_URL", 
        "BONOB_WEB_ADDRESS"
      ])("when %s is specified", (k) => {
        it("should be used", () => {
          const url = "http://bonob1.example.com:8877/";

          process.env["BNB_URL"] = "";
          process.env["BONOB_URL"] = "";
          process.env["BONOB_WEB_ADDRESS"] = "";
          process.env[k] = url;

          expect(config().bonobUrl.href()).toEqual(url);
        });
    });

    describe("when none of BNB_URL, BONOB_URL, BONOB_WEB_ADDRESS are specified", () => {
      describe("when BONOB_PORT is not specified", () => {
        it(`should default to http://${hostname()}:4534`, () => {
          expect(config().bonobUrl.href()).toEqual(
            `http://${hostname()}:4534/`
          );
        });
      });

      describe("when BNB_PORT is specified as 3322", () => {
        it(`should default to http://${hostname()}:3322`, () => {
          process.env["BNB_PORT"] = "3322";
          expect(config().bonobUrl.href()).toEqual(
            `http://${hostname()}:3322/`
          );
        });
      });

      describe("when BONOB_PORT is specified as 3322", () => {
        it(`should default to http://${hostname()}:3322`, () => {
          process.env["BONOB_PORT"] = "3322";
          expect(config().bonobUrl.href()).toEqual(
            `http://${hostname()}:3322/`
          );
        });
      });
    });
  });

  describe("icons", () => {
    describe("foregroundColor", () => {
      describe.each([
        "BNB_ICON_FOREGROUND_COLOR",
        "BONOB_ICON_FOREGROUND_COLOR",
      ])("%s", (k) => {
        describe(`when ${k} is not specified`, () => {
          it(`should default to undefined`, () => {
            expect(config().icons.foregroundColor).toEqual(undefined);
          });
        });

        describe(`when ${k} is ''`, () => {
          it(`should default to undefined`, () => {
            process.env[k] = "";
            expect(config().icons.foregroundColor).toEqual(undefined);
          });
        });

        describe(`when ${k} is specified as a color`, () => {
          it(`should use it`, () => {
            process.env[k] = "pink";
            expect(config().icons.foregroundColor).toEqual("pink");
          });
        });

        describe(`when ${k} is specified as hex`, () => {
          it(`should use it`, () => {
            process.env[k] = "#1db954";
            expect(config().icons.foregroundColor).toEqual("#1db954");
          });
        });

        describe(`when ${k} is an invalid string`, () => {
          it(`should blow up`, () => {
            process.env[k] = "!dfasd";
            expect(() => config()).toThrow(
              `Invalid value specified for 'BNB_ICON_FOREGROUND_COLOR', must match ${COLOR}`
            );
          });
        });
      });
    });

    describe("backgroundColor", () => {
      describe.each([
        "BNB_ICON_BACKGROUND_COLOR",
        "BONOB_ICON_BACKGROUND_COLOR",
      ])("%s", (k) => {
        describe(`when ${k} is not specified`, () => {
          it(`should default to undefined`, () => {
            expect(config().icons.backgroundColor).toEqual(undefined);
          });
        });

        describe(`when ${k} is ''`, () => {
          it(`should default to undefined`, () => {
            process.env[k] = "";
            expect(config().icons.backgroundColor).toEqual(undefined);
          });
        });

        describe(`when ${k} is specified as a color`, () => {
          it(`should use it`, () => {
            process.env[k] = "blue";
            expect(config().icons.backgroundColor).toEqual("blue");
          });
        });

        describe(`when ${k} is specified as hex`, () => {
          it(`should use it`, () => {
            process.env[k] = "#1db954";
            expect(config().icons.backgroundColor).toEqual("#1db954");
          });
        });

        describe(`when ${k} is an invalid string`, () => {
          it(`should blow up`, () => {
            process.env[k] = "!red";
            expect(() => config()).toThrow(
              `Invalid value specified for 'BNB_ICON_BACKGROUND_COLOR', must match ${COLOR}`
            );
          });
        });
      });
    });
  });

  describe("secret", () => {
    it("should default to bonob", () => {
      expect(config().secret).toEqual("bonob");
    });

    describe.each([
      "BNB_SECRET", 
      "BONOB_SECRET"
    ])("%s", (k) => {
      it(`should be overridable using ${k}`, () => {
        process.env[k] = "new secret";
        expect(config().secret).toEqual("new secret");
      });
    });
  });

  describe("authTimeout", () => {
    it("should default to 1h", () => {
      expect(config().authTimeout).toEqual("1h");
    });

    it(`should be overridable using BNB_AUTH_TIMEOUT`, () => {
      process.env["BNB_AUTH_TIMEOUT"] = "33s";
      expect(config().authTimeout).toEqual("33s");
    });
  });

  describe("sonos", () => {
    describe("serviceName", () => {
      it("should default to bonob", () => {
        expect(config().sonos.serviceName).toEqual("bonob");
      });

      describe.each([
        "BNB_SONOS_SERVICE_NAME", 
        "BONOB_SONOS_SERVICE_NAME"
      ])(
        "%s",
        (k) => {
          it("should be overridable", () => {
            process.env[k] = "foobar1000";
            expect(config().sonos.serviceName).toEqual("foobar1000");
          });
        }
      );
    });

    describe.each([
      "BNB_SONOS_DEVICE_DISCOVERY",
      "BONOB_SONOS_DEVICE_DISCOVERY",
    ])("%s", (k) => {
      describeBooleanConfigValue(
        "deviceDiscovery",
        k,
        true,
        (config) => config.sonos.discovery.enabled
      );
    });

    describe("seedHost", () => {
      it("should default to undefined", () => {
        expect(config().sonos.discovery.seedHost).toBeUndefined();
      });

      describe.each([
        "BNB_SONOS_SEED_HOST", 
        "BONOB_SONOS_SEED_HOST"
      ])(
        "%s",
        (k) => {
          it("should be overridable", () => {
            process.env[k] = "123.456.789.0";
            expect(config().sonos.discovery.seedHost).toEqual("123.456.789.0");
          });
        }
      );
    });

    describe.each([
      "BNB_SONOS_AUTO_REGISTER", 
      "BONOB_SONOS_AUTO_REGISTER"
    ])(
      "%s",
      (k) => {
        describeBooleanConfigValue(
          "autoRegister",
          k,
          false,
          (config) => config.sonos.autoRegister
        );
      }
    );

    describe("sid", () => {
      it("should default to 246", () => {
        expect(config().sonos.sid).toEqual(246);
      });

      describe.each([
        "BNB_SONOS_SERVICE_ID", 
        "BONOB_SONOS_SERVICE_ID"
      ])(
        "%s",
        (k) => {
          it("should be overridable", () => {
            process.env[k] = "786";
            expect(config().sonos.sid).toEqual(786);
          });
        }
      );
    });
  });

  describe("subsonic", () => {
    describe("url", () => {
      describe.each([
        "BNB_SUBSONIC_URL",
        "BONOB_SUBSONIC_URL",
        "BONOB_NAVIDROME_URL",
      ])("%s", (k) => {
        describe(`when ${k} is not specified`, () => {
          it(`should default to http://${hostname()}:4533`, () => {
            expect(config().subsonic.url).toEqual(`http://${hostname()}:4533`);
          });
        });

        describe(`when ${k} is ''`, () => {
          it(`should default to http://${hostname()}:4533`, () => {
            process.env[k] = "";
            expect(config().subsonic.url).toEqual(`http://${hostname()}:4533`);
          });
        });

        describe(`when ${k} is specified`, () => {
          it(`should use it for ${k}`, () => {
            const url = "http://navidrome.example.com:1234";
            process.env[k] = url;
            expect(config().subsonic.url).toEqual(url);
          });
        });
      });
    });

    describe("customClientsFor", () => {
      it("should default to undefined", () => {
        expect(config().subsonic.customClientsFor).toBeUndefined();
      });

      describe.each([
        "BNB_SUBSONIC_CUSTOM_CLIENTS",
        "BONOB_SUBSONIC_CUSTOM_CLIENTS",
        "BONOB_NAVIDROME_CUSTOM_CLIENTS",
      ])("%s", (k) => {
        it(`should be overridable for ${k}`, () => {
          process.env[k] = "whoop/whoop";
          expect(config().subsonic.customClientsFor).toEqual("whoop/whoop");
        });
      });
    });

    describe("artistImageCache", () => {
      it("should default to undefined", () => {
        expect(config().subsonic.artistImageCache).toBeUndefined();
      });

      it(`should be overridable for BNB_SUBSONIC_ARTIST_IMAGE_CACHE`, () => {
        process.env["BNB_SUBSONIC_ARTIST_IMAGE_CACHE"] = "/some/path";
        expect(config().subsonic.artistImageCache).toEqual("/some/path");
      });
    });
  });

  describe.each([
    "BNB_SCROBBLE_TRACKS", 
    "BONOB_SCROBBLE_TRACKS"
  ])("%s", (k) => {
    describeBooleanConfigValue(
      "scrobbleTracks",
      k,
      true,
      (config) => config.scrobbleTracks
    );
  });

  describe.each([
    "BNB_REPORT_NOW_PLAYING", 
    "BONOB_REPORT_NOW_PLAYING"
  ])(
    "%s",
    (k) => {
      describeBooleanConfigValue(
        "reportNowPlaying",
        k,
        true,
        (config) => config.reportNowPlaying
      );
    }
  );
});
