import type {
  Folder,
  GrpcRequest,
  HttpRequest,
  HttpVersion,
  InheritedBoolSetting,
  InheritedHttpVersionSetting,
  InheritedIntSetting,
  WebsocketRequest,
  Workspace,
} from "@yaakapp-internal/models";
import { patchModel } from "@yaakapp-internal/models";
import { useModelAncestors } from "../hooks/useModelAncestors";
import {
  modelSupportsSetting,
  type RequestSettingDefinition,
  SETTING_FOLLOW_REDIRECTS,
  SETTING_HTTP_VERSION,
  SETTING_REQUEST_MESSAGE_SIZE,
  SETTING_REQUEST_TIMEOUT,
  SETTING_SEND_COOKIES,
  SETTING_STORE_COOKIES,
  SETTING_VALIDATE_CERTIFICATES,
} from "../lib/requestSettings";
import { Checkbox } from "./core/Checkbox";
import { PlainInput } from "./core/PlainInput";
import { Select } from "./core/Select";
import {
  SettingOverrideRow,
  SettingRow,
  SettingRowBoolean,
  SettingsList,
  SettingsSection,
} from "./core/SettingRow";

const BYTES_PER_MB = 1024 * 1024;
const MAX_REQUEST_MESSAGE_SIZE_BYTES = 2_147_483_647;
const MAX_MESSAGE_SIZE_MB = MAX_REQUEST_MESSAGE_SIZE_BYTES / BYTES_PER_MB;

interface Props {
  showSectionTitles?: boolean;
  model: ModelWithSettings;
}

type ModelWithSettings = Workspace | Folder | HttpRequest | WebsocketRequest | GrpcRequest;
type ModelWithHttpSettings = Workspace | Folder | HttpRequest;
type ModelWithTlsSettings = Workspace | Folder | HttpRequest | WebsocketRequest | GrpcRequest;
type ModelWithCookieSettings = Workspace | Folder | HttpRequest | WebsocketRequest;
type ModelWithMessageSizeSettings = Workspace | Folder | WebsocketRequest | GrpcRequest;
type BooleanSetting = boolean | InheritedBoolSetting;
type IntegerSetting = number | InheritedIntSetting;
type HttpVersionSetting = HttpVersion | InheritedHttpVersionSetting;
type CookieSettingsPatch = {
  settingSendCookies?: ModelWithCookieSettings["settingSendCookies"];
  settingStoreCookies?: ModelWithCookieSettings["settingStoreCookies"];
};
type HttpSettingsPatch = {
  settingFollowRedirects?: ModelWithHttpSettings["settingFollowRedirects"];
  settingHttpVersion?: ModelWithHttpSettings["settingHttpVersion"];
  settingRequestTimeout?: ModelWithHttpSettings["settingRequestTimeout"];
};
type TlsSettingsPatch = {
  settingValidateCertificates?: ModelWithTlsSettings["settingValidateCertificates"];
};
type MessageSizeSettingsPatch = {
  settingRequestMessageSize?: ModelWithMessageSizeSettings["settingRequestMessageSize"];
};

export function ModelSettingsEditor({ model, showSectionTitles = false }: Props) {
  const ancestors = useModelAncestors(model);
  const supportsHttpSettings = modelSupportsHttpSettings(model);
  const supportsCookieSettings = modelSupportsCookieSettings(model);
  const supportsTlsSettings = modelSupportsTlsSettings(model);
  const supportsMessageSizeSettings = modelSupportsMessageSizeSettings(model);

  return (
    <SettingsList className="space-y-8">
      {supportsTlsSettings && (
        <SettingsSection title={showSectionTitles ? "Requests" : null}>
          {supportsHttpSettings && (
            <IntegerSettingRow
              settingDefinition={SETTING_REQUEST_TIMEOUT}
              setting={model.settingRequestTimeout}
              inheritedValue={resolveInheritedValue(
                ancestors,
                SETTING_REQUEST_TIMEOUT.modelKey,
                model.settingRequestTimeout,
              )}
              onChange={(settingRequestTimeout) =>
                patchHttpSettings(model, {
                  settingRequestTimeout,
                })
              }
            />
          )}
          {supportsMessageSizeSettings && (
            <MessageSizeSettingRow
              settingDefinition={SETTING_REQUEST_MESSAGE_SIZE}
              setting={model.settingRequestMessageSize}
              inheritedValue={resolveInheritedValue(
                ancestors,
                SETTING_REQUEST_MESSAGE_SIZE.modelKey,
                model.settingRequestMessageSize,
              )}
              onChange={(settingRequestMessageSize) =>
                patchMessageSizeSettings(model, {
                  settingRequestMessageSize,
                })
              }
            />
          )}
          <BooleanSettingRow
            settingDefinition={SETTING_VALIDATE_CERTIFICATES}
            setting={model.settingValidateCertificates}
            inheritedValue={resolveInheritedValue(
              ancestors,
              SETTING_VALIDATE_CERTIFICATES.modelKey,
              model.settingValidateCertificates,
            )}
            onChange={(settingValidateCertificates) =>
              patchTlsSettings(model, {
                settingValidateCertificates,
              })
            }
          />
          {supportsHttpSettings && (
            <BooleanSettingRow
              settingDefinition={SETTING_FOLLOW_REDIRECTS}
              setting={model.settingFollowRedirects}
              inheritedValue={resolveInheritedValue(
                ancestors,
                SETTING_FOLLOW_REDIRECTS.modelKey,
                model.settingFollowRedirects,
              )}
              onChange={(settingFollowRedirects) =>
                patchHttpSettings(model, {
                  settingFollowRedirects,
                })
              }
            />
          )}
          {supportsHttpSettings && (
            <HttpVersionSettingRow
              settingDefinition={SETTING_HTTP_VERSION}
              setting={model.settingHttpVersion}
              inheritedValue={resolveInheritedValue(
                ancestors,
                SETTING_HTTP_VERSION.modelKey,
                model.settingHttpVersion,
              )}
              onChange={(settingHttpVersion) =>
                patchHttpSettings(model, {
                  settingHttpVersion,
                })
              }
            />
          )}
        </SettingsSection>
      )}
      {supportsCookieSettings && (
        <SettingsSection title={supportsTlsSettings || showSectionTitles ? "Cookies" : null}>
          <BooleanSettingRow
            settingDefinition={SETTING_SEND_COOKIES}
            setting={model.settingSendCookies}
            inheritedValue={resolveInheritedValue(
              ancestors,
              SETTING_SEND_COOKIES.modelKey,
              model.settingSendCookies,
            )}
            onChange={(settingSendCookies) =>
              patchCookieSettings(model, {
                settingSendCookies,
              })
            }
          />
          <BooleanSettingRow
            settingDefinition={SETTING_STORE_COOKIES}
            setting={model.settingStoreCookies}
            inheritedValue={resolveInheritedValue(
              ancestors,
              SETTING_STORE_COOKIES.modelKey,
              model.settingStoreCookies,
            )}
            onChange={(settingStoreCookies) =>
              patchCookieSettings(model, {
                settingStoreCookies,
              })
            }
          />
        </SettingsSection>
      )}
    </SettingsList>
  );
}

export function countOverriddenSettings(model: ModelWithSettings) {
  const settings: (BooleanSetting | IntegerSetting | HttpVersionSetting)[] = [];

  if (modelSupportsCookieSettings(model)) {
    settings.push(model.settingSendCookies, model.settingStoreCookies);
  }

  settings.push(model.settingValidateCertificates);

  if (modelSupportsHttpSettings(model)) {
    settings.push(
      model.settingFollowRedirects,
      model.settingRequestTimeout,
      model.settingHttpVersion,
    );
  }

  if (modelSupportsMessageSizeSettings(model)) {
    settings.push(model.settingRequestMessageSize);
  }

  return settings.filter((setting) => isInheritedSetting(setting) && setting.enabled === true)
    .length;
}

function patchCookieSettings(model: ModelWithCookieSettings, patch: Partial<CookieSettingsPatch>) {
  switch (model.model) {
    case "workspace":
      return patchModel(model, patch as Partial<Workspace>);
    case "folder":
      return patchModel(model, patch as Partial<Folder>);
    case "http_request":
      return patchModel(model, patch as Partial<HttpRequest>);
    case "websocket_request":
      return patchModel(model, patch as Partial<WebsocketRequest>);
  }
}

function patchHttpSettings(model: ModelWithHttpSettings, patch: Partial<HttpSettingsPatch>) {
  switch (model.model) {
    case "workspace":
      return patchModel(model, patch as Partial<Workspace>);
    case "folder":
      return patchModel(model, patch as Partial<Folder>);
    case "http_request":
      return patchModel(model, patch as Partial<HttpRequest>);
  }
}

function patchTlsSettings(model: ModelWithTlsSettings, patch: Partial<TlsSettingsPatch>) {
  switch (model.model) {
    case "workspace":
      return patchModel(model, patch as Partial<Workspace>);
    case "folder":
      return patchModel(model, patch as Partial<Folder>);
    case "http_request":
      return patchModel(model, patch as Partial<HttpRequest>);
    case "websocket_request":
      return patchModel(model, patch as Partial<WebsocketRequest>);
    case "grpc_request":
      return patchModel(model, patch as Partial<GrpcRequest>);
  }
}

function patchMessageSizeSettings(
  model: ModelWithMessageSizeSettings,
  patch: Partial<MessageSizeSettingsPatch>,
) {
  switch (model.model) {
    case "workspace":
      return patchModel(model, patch as Partial<Workspace>);
    case "folder":
      return patchModel(model, patch as Partial<Folder>);
    case "websocket_request":
      return patchModel(model, patch as Partial<WebsocketRequest>);
    case "grpc_request":
      return patchModel(model, patch as Partial<GrpcRequest>);
  }
}

function modelSupportsHttpSettings(model: ModelWithSettings): model is ModelWithHttpSettings {
  return modelSupportsSetting(model, SETTING_REQUEST_TIMEOUT);
}

function modelSupportsCookieSettings(model: ModelWithSettings): model is ModelWithCookieSettings {
  return modelSupportsSetting(model, SETTING_SEND_COOKIES);
}

function modelSupportsTlsSettings(model: ModelWithSettings): model is ModelWithTlsSettings {
  return modelSupportsSetting(model, SETTING_VALIDATE_CERTIFICATES);
}

function modelSupportsMessageSizeSettings(
  model: ModelWithSettings,
): model is ModelWithMessageSizeSettings {
  return modelSupportsSetting(model, SETTING_REQUEST_MESSAGE_SIZE);
}

function BooleanSettingRow({
  inheritedValue,
  setting,
  settingDefinition,
  onChange,
}: {
  inheritedValue: boolean;
  setting: BooleanSetting;
  settingDefinition: RequestSettingDefinition;
  onChange: (setting: BooleanSetting) => void;
}) {
  const inherited = isInheritedSetting(setting);
  const overridden = inherited ? setting.enabled === true : false;
  const value = inherited ? (overridden ? setting.value : inheritedValue) : setting;

  if (!inherited) {
    return (
      <SettingRowBoolean
        checked={value}
        title={settingDefinition.title}
        description={settingDefinition.description}
        onChange={(value) => onChange(value)}
      />
    );
  }

  return (
    <SettingOverrideRow
      title={settingDefinition.title}
      description={settingDefinition.description}
      overridden={overridden}
      onResetOverride={() => onChange({ ...setting, enabled: false })}
    >
      <Checkbox
        hideLabel
        size="md"
        title={settingDefinition.title}
        checked={value}
        onChange={(value) => onChange({ ...setting, enabled: true, value })}
      />
    </SettingOverrideRow>
  );
}

const HTTP_VERSION_OPTIONS: { label: string; value: HttpVersion }[] = [
  { label: "Automatic", value: "auto" },
  { label: "HTTP/1.1", value: "http1" },
  { label: "HTTP/2", value: "http2" },
];

function HttpVersionSettingRow({
  inheritedValue,
  setting,
  settingDefinition,
  onChange,
}: {
  inheritedValue: HttpVersion;
  setting: HttpVersionSetting;
  settingDefinition: RequestSettingDefinition<"settingHttpVersion">;
  onChange: (setting: HttpVersionSetting) => void;
}) {
  const inherited = isInheritedSetting(setting);
  const overridden = inherited ? setting.enabled === true : false;
  const value = inherited ? (overridden ? setting.value : inheritedValue) : setting;

  if (!inherited) {
    return (
      <SettingRow title={settingDefinition.title} description={settingDefinition.description}>
        <Select
          hideLabel
          name={settingDefinition.modelKey}
          label={settingDefinition.title}
          size="sm"
          value={value}
          options={HTTP_VERSION_OPTIONS}
          onChange={(value) => onChange(value)}
        />
      </SettingRow>
    );
  }

  return (
    <SettingOverrideRow
      title={settingDefinition.title}
      description={settingDefinition.description}
      overridden={overridden}
      onResetOverride={() => onChange({ ...setting, enabled: false })}
    >
      <Select
        hideLabel
        name={settingDefinition.modelKey}
        label={settingDefinition.title}
        size="sm"
        value={value}
        options={HTTP_VERSION_OPTIONS}
        onChange={(value) => onChange({ ...setting, enabled: true, value })}
      />
    </SettingOverrideRow>
  );
}

function IntegerSettingRow({
  inheritedValue,
  setting,
  settingDefinition,
  onChange,
}: {
  inheritedValue: number;
  setting: IntegerSetting;
  settingDefinition: RequestSettingDefinition<"settingRequestTimeout">;
  onChange: (setting: IntegerSetting) => void;
}) {
  const inherited = isInheritedSetting(setting);
  const overridden = inherited ? setting.enabled === true : false;
  const value = inherited ? (overridden ? setting.value : inheritedValue) : setting;

  if (!inherited) {
    return (
      <SettingRow title={settingDefinition.title} description={settingDefinition.description}>
        <NumberUnitInput
          name={settingDefinition.modelKey}
          label={settingDefinition.title}
          unit="ms"
          value={`${value}`}
          placeholder={`${settingDefinition.defaultValue}`}
          validate={isValidInteger}
          onChange={(value) => onChange(parseInteger(value))}
        />
      </SettingRow>
    );
  }

  return (
    <SettingOverrideRow
      title={settingDefinition.title}
      description={settingDefinition.description}
      overridden={overridden}
      onResetOverride={() => onChange({ ...setting, enabled: false })}
    >
      <NumberUnitInput
        name={settingDefinition.modelKey}
        label={settingDefinition.title}
        unit="ms"
        value={`${value}`}
        placeholder={`${settingDefinition.defaultValue}`}
        validate={isValidInteger}
        onChange={(value) =>
          onChange({
            ...setting,
            enabled: true,
            value: parseInteger(value),
          })
        }
      />
    </SettingOverrideRow>
  );
}

function MessageSizeSettingRow({
  inheritedValue,
  setting,
  settingDefinition,
  onChange,
}: {
  inheritedValue: number;
  setting: IntegerSetting;
  settingDefinition: RequestSettingDefinition<"settingRequestMessageSize">;
  onChange: (setting: IntegerSetting) => void;
}) {
  const inherited = isInheritedSetting(setting);
  const overridden = inherited ? setting.enabled === true : false;
  const value = inherited ? (overridden ? setting.value : inheritedValue) : setting;
  const displayValue = formatMegabytes(value);
  const placeholder = "0";

  if (!inherited) {
    return (
      <SettingRow title={settingDefinition.title} description={settingDefinition.description}>
        <MessageSizeInput
          name={settingDefinition.modelKey}
          label={settingDefinition.title}
          value={displayValue}
          placeholder={placeholder}
          onChange={(value) => onChange(parseMegabytes(value))}
        />
      </SettingRow>
    );
  }

  return (
    <SettingOverrideRow
      title={settingDefinition.title}
      description={settingDefinition.description}
      overridden={overridden}
      onResetOverride={() => onChange({ ...setting, enabled: false })}
    >
      <MessageSizeInput
        name={settingDefinition.modelKey}
        label={settingDefinition.title}
        value={displayValue}
        placeholder={placeholder}
        onChange={(value) =>
          onChange({
            ...setting,
            enabled: true,
            value: parseMegabytes(value),
          })
        }
      />
    </SettingOverrideRow>
  );
}

function MessageSizeInput({
  label,
  name,
  onChange,
  placeholder,
  value,
}: {
  label: string;
  name: string;
  onChange: (value: string) => void;
  placeholder: string;
  value: string;
}) {
  return (
    <NumberUnitInput
      name={name}
      label={label}
      unit="MB"
      value={value}
      inputMode="decimal"
      step="any"
      placeholder={placeholder}
      validate={isValidMegabytes}
      onChange={onChange}
    />
  );
}

function NumberUnitInput({
  inputMode,
  label,
  name,
  onChange,
  placeholder,
  step,
  unit,
  validate,
  value,
}: {
  inputMode?: "decimal" | "numeric";
  label: string;
  name: string;
  onChange: (value: string) => void;
  placeholder: string;
  step?: number | "any";
  unit: string;
  validate: (value: string) => boolean;
  value: string;
}) {
  return (
    <PlainInput
      hideLabel
      name={name}
      label={label}
      size="sm"
      type="number"
      inputMode={inputMode}
      step={step}
      placeholder={placeholder}
      defaultValue={value}
      className="[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
      containerClassName="w-48!"
      validate={validate}
      rightSlot={
        <span className="flex self-stretch items-center border-l border-border-subtle px-2 text-xs font-medium text-text-subtle">
          {unit}
        </span>
      }
      onChange={onChange}
    />
  );
}

function isInheritedSetting<T>(
  setting: T | { enabled?: boolean; value: T },
): setting is { enabled?: boolean; value: T } {
  return typeof setting === "object" && setting != null && "value" in setting;
}

function resolveInheritedValue(
  ancestors: (Folder | Workspace)[],
  key: "settingRequestTimeout" | "settingRequestMessageSize",
  fallback: IntegerSetting,
): number;
function resolveInheritedValue(
  ancestors: (Folder | Workspace)[],
  key: BooleanWorkspaceSettingKey,
  fallback: BooleanSetting,
): boolean;
function resolveInheritedValue(
  ancestors: (Folder | Workspace)[],
  key: "settingHttpVersion",
  fallback: HttpVersionSetting,
): HttpVersion;
function resolveInheritedValue(
  ancestors: (Folder | Workspace)[],
  key: keyof WorkspaceSettings,
  fallback: BooleanSetting | IntegerSetting | HttpVersionSetting,
) {
  for (const ancestor of ancestors) {
    const setting = ancestor[key] as BooleanSetting | IntegerSetting | HttpVersionSetting;
    if (isInheritedSetting(setting)) {
      if (setting.enabled === true) {
        return setting.value;
      }
      continue;
    }
    return setting;
  }

  return isInheritedSetting(fallback) ? fallback.value : fallback;
}

type WorkspaceSettings = Pick<
  Workspace,
  | "settingFollowRedirects"
  | "settingHttpVersion"
  | "settingRequestMessageSize"
  | "settingRequestTimeout"
  | "settingSendCookies"
  | "settingStoreCookies"
  | "settingValidateCertificates"
>;

type BooleanWorkspaceSettingKey = Exclude<
  keyof WorkspaceSettings,
  "settingRequestTimeout" | "settingRequestMessageSize" | "settingHttpVersion"
>;

function formatMegabytes(bytes: number) {
  const megabytes = bytes / BYTES_PER_MB;
  return Number.isInteger(megabytes) ? `${megabytes}` : megabytes.toFixed(3).replace(/\.?0+$/, "");
}

function parseMegabytes(value: string) {
  const megabytes = Number(value);
  return Number.isFinite(megabytes) ? Math.round(megabytes * BYTES_PER_MB) : 0;
}

function parseInteger(value: string) {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? Math.trunc(parsed) : 0;
}

function isValidInteger(value: string) {
  const parsed = Number(value);
  return value === "" || (Number.isInteger(parsed) && parsed >= 0);
}

function isValidMegabytes(value: string) {
  if (value === "") return true;
  const megabytes = Number(value);
  return Number.isFinite(megabytes) && megabytes >= 0 && megabytes <= MAX_MESSAGE_SIZE_MB;
}
