import { faToggleOff, faToggleOn } from "@fortawesome/pro-regular-svg-icons";
import { useLocation, useNavigate, useParams } from "@solidjs/router";
import { eq } from "drizzle-orm";
import { PgUpdateSetSource } from "drizzle-orm/pg-core";
import moment from "moment-timezone";
import {
  For,
  Index,
  Show,
  batch,
  createEffect,
  createMemo,
  createResource,
  createSignal,
} from "solid-js";
import { createStore } from "solid-js/store";
import { getRequestEvent } from "solid-js/web";
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon";
import Layout from "~/components/Layout";
import db from "~/drizzle";
import { guilds } from "~/drizzle/schema";
import { guildChannels, userGuilds } from "~/lib/cachedDiscord";
import { combineInterval, splitInterval } from "~/lib/responseBuilders";
import { zodBigIntId } from "~/lib/zod";
import "../../styles/pages/config.scss";

if (typeof import.meta.env.VITE_DISCORD_BOT_TOKEN === "undefined")
  throw new Error("No env VITE_DISCORD_BOT_TOKEN found!");

const guessTZ = () => Intl.DateTimeFormat().resolvedOptions().timeZone;

const getPayload = async (
  id: string,
): Promise<
  | { success: false; message: string }
  | {
      success: true;
      guild: {
        id: string;
        name: string;
        icon: string | null | undefined;
        tpChannelId: string;
        channels: {
          id: string;
          name: string;
        }[];
        tpInterval: number;
        pingableRoles: boolean;
        timezone: string;
      };
      tzNames: string[];
    }
> => {
  "use server";

  let guildId: bigint;
  try {
    guildId = zodBigIntId.parse(id);
  } catch (e) {
    return { success: false, message: "ID is invalid" };
  }

  const event = getRequestEvent();
  if (!event) return { success: false, message: "No request event!" };

  const { user } = event.nativeEvent.context;
  if (!user) return { success: false, message: "User not logged in!" };

  const guildsData = await userGuilds(user.id);
  const guild = guildsData?.find((e) => e.id === String(guildId));

  if (!guild)
    return {
      success: false,
      message: "User is in no such guild with requested id!",
    };
  if (!(parseInt(guild.permissions) & (1 << 5)))
    return {
      success: false,
      message:
        "User is no MANAGE_GUILD permissions on this guild with requested id!",
    };

  const channelsData = await guildChannels(guildId);

  const channels: {
    id: string;
    name: string;
  }[] = [];
  channelsData?.forEach((channel) => {
    if (channel.type !== 0) return;
    channels.push({
      id: channel.id,
      name: channel.name,
    });
  });

  const config = await db.query.guilds
    .findFirst({ where: eq(guilds.id, guildId) })
    .execute();

  if (!config) return { success: false, message: "No config found!" };

  console.log(new URL(event.request.url).pathname, "success");

  return {
    success: true,
    guild: {
      id: guild.id,
      name: guild.name,
      icon: guild.icon,
      tpChannelId: String(config.tpChannelId ?? ""),
      channels,
      tpInterval: config.tpInterval,
      pingableRoles: config.tpRolesEnabled,
      timezone: config.timezone,
    },
    tzNames: moment.tz.names(),
  };
};

const saveConfig = async (
  id: string,
  updateValues: PgUpdateSetSource<typeof guilds>,
): Promise<{ success: false; message: string } | { success: true }> => {
  "use server";

  let guildId: bigint;
  try {
    guildId = zodBigIntId.parse(id);
  } catch (e) {
    return { success: false, message: "ID is invalid" };
  }

  const event = getRequestEvent();
  if (!event) return { success: false, message: "No request event!" };

  console.log({ updateValues });

  await db
    .update(guilds)
    .set(updateValues)
    .where(eq(guilds.id, guildId))
    .execute();

  console.log(new URL(event.request.url).pathname, "config save success");

  return { success: true };
};

function config() {
  const params = useParams();
  const navigator = useNavigate();
  const location = useLocation();

  const [timezoneRef, setTimezoneRef] = createSignal<HTMLInputElement>();
  const [tpEnabledRef, setTpEnabledRef] = createSignal<HTMLInputElement>();
  const [channelRef, setChannelRef] = createSignal<HTMLSelectElement>();
  const [targetMinuteRef, setTargetMinuteRef] =
    createSignal<HTMLSelectElement>();
  const [targetHourRef, setTargetHourRef] = createSignal<HTMLSelectElement>();
  const [targetDayRef, setTargetDayRef] = createSignal<HTMLSelectElement>();
  const [pingableRolesRef, setPingableRolesRef] =
    createSignal<HTMLInputElement>();

  const [payload, { refetch }] = createResource(
    params.guildId,
    async (id) => {
      const payload = await getPayload(id);

      if (!payload) {
        console.error(location.pathname, payload);
        return undefined;
      }

      if (!payload.success) {
        console.log(payload);
        console.log(location.pathname, payload.message, "No success");
        navigator("/config", { replace: false });
        return undefined;
      }

      return payload;
    },
    {
      deferStream: true,
    },
  );
  const [config, setConfig] = createStore({
    features: {
      timePlanning: {
        enabled: false,
        channelId: "",
        targetMinute: 0,
        targetHour: 0,
        targetDay: 0,
        pingableRoles: false,
        timezone: guessTZ(),
      },
    },
  });

  const updateValues = createMemo(() => {
    const guild = !payload.loading && payload()?.guild;
    if (!guild) return {};
    const data = config.features.timePlanning;
    const tpInterval = combineInterval(
      data.targetMinute,
      data.targetHour,
      data.targetDay,
    );

    const result: PgUpdateSetSource<typeof guilds> = {
      timezone: data.timezone !== guild.timezone ? data.timezone : undefined,
      tpChannelId:
        data.enabled || !data.channelId
          ? data.channelId && data.channelId !== guild.tpChannelId
            ? BigInt(data.channelId)
            : undefined
          : null,
      tpInterval:
        data.enabled && data.channelId && tpInterval !== guild.tpInterval
          ? tpInterval
          : undefined,
      tpRolesEnabled:
        data.enabled &&
        data.channelId &&
        data.pingableRoles !== guild.pingableRoles
          ? data.pingableRoles
          : undefined,
    };
    return result;
  });
  const willUpdateValues = () =>
    Object.values(updateValues()).filter((e) => typeof e !== "undefined")
      .length;

  createEffect(() => {
    const guild = payload()?.guild;
    if (!guild) return;
    const channelId = guild.tpChannelId;
    const pingableRoles = guild.pingableRoles;
    const { targetMinute, targetHour, targetDay } = splitInterval(
      guild.tpInterval,
    );
    const timezone = guild.timezone;

    batch(() => {
      setConfig("features", "timePlanning", "enabled", !!channelId);
      setConfig("features", "timePlanning", "channelId", channelId);
      setConfig("features", "timePlanning", "targetMinute", targetMinute);
      setConfig("features", "timePlanning", "targetHour", targetHour);
      setConfig("features", "timePlanning", "targetDay", targetDay);
      setConfig("features", "timePlanning", "pingableRoles", pingableRoles);
      setConfig("features", "timePlanning", "timezone", timezone);
    });
  });
  createEffect(() => {
    const ref = timezoneRef();
    if (ref) ref.value = config.features.timePlanning.timezone;
  });
  createEffect(() => {
    const ref = tpEnabledRef();
    if (ref) ref.checked = config.features.timePlanning.enabled;
  });
  createEffect(() => {
    const ref = channelRef();
    if (ref) ref.value = config.features.timePlanning.channelId;
  });
  createEffect(() => {
    const ref = targetMinuteRef();
    if (ref) ref.value = String(config.features.timePlanning.targetMinute);
  });
  createEffect(() => {
    const ref = targetHourRef();
    if (ref) ref.value = String(config.features.timePlanning.targetHour);
  });
  createEffect(() => {
    const ref = targetDayRef();
    if (ref) ref.value = String(config.features.timePlanning.targetDay);
  });
  createEffect(() => {
    const ref = pingableRolesRef();
    if (ref) ref.checked = config.features.timePlanning.pingableRoles;
  });

  return (
    <Layout site="config">
      <div class="group">
        <h3>Configure li&apos;l Judd in</h3>
        <div>
          <div>
            <div class="flex-row centered">
              <img
                class="guildpfp"
                src={
                  payload()?.guild.icon
                    ? `https://cdn.discordapp.com/icons/${payload()?.guild.id}/${
                        payload()?.guild.icon
                      }.webp?size=240`
                    : "https://cdn.discordapp.com/icons/1040502664506646548/bb5a51c4659cf47bdd942bb11e974da7.webp?size=240"
                }
                alt="Server pfp"
              />
              <h1>{payload()?.guild.name}</h1>
            </div>
          </div>

          <section class="box">
            <h2>Guild</h2>
            <p>General settings for this guild.</p>
            <div class="flex-row">
              <label for="timezone">Timezone for your server:</label>
              <input
                type="text"
                list="timezones"
                id="timezone"
                ref={(e) => setTimezoneRef(e)}
                value={payload()?.guild.timezone}
                onInput={(e) =>
                  setConfig(
                    "features",
                    "timePlanning",
                    "timezone",
                    e.target.value,
                  )
                }
              />

              <datalist id="timezones">
                <Index each={payload()?.tzNames}>
                  {(zone) => <option value={zone()} />}
                </Index>
              </datalist>

              <button
                disabled={guessTZ() === config.features.timePlanning.timezone}
                title={"Detected: " + guessTZ()}
                onClick={() =>
                  setConfig("features", "timePlanning", "timezone", guessTZ())
                }
              >
                Auto-detect
              </button>
            </div>
          </section>

          <section class="box">
            <h2>Features</h2>
            <p>Configure the features of the bot</p>
            <label for="timePlanning" class="flex-row">
              <p>Time Planning </p>
              <FontAwesomeIcon
                icon={
                  config.features.timePlanning.enabled
                    ? faToggleOn
                    : faToggleOff
                }
                size="xl"
              />
            </label>
            <input
              hidden
              type="checkbox"
              id="timePlanning"
              ref={(e) => setTpEnabledRef(e)}
              checked={!!payload()?.guild.tpChannelId}
              onInput={(e) =>
                setConfig(
                  "features",
                  "timePlanning",
                  "enabled",
                  e.target.checked,
                )
              }
            />
            <div
              class="sub"
              classList={{ disabled: !config.features.timePlanning.enabled }}
            >
              <div class="flex-row">
                <label>Target channel:</label>
                <select
                  ref={(e) => setChannelRef(e)}
                  value={payload()?.guild.tpChannelId}
                  onInput={(e) =>
                    setConfig(
                      "features",
                      "timePlanning",
                      "channelId",
                      e.target.value,
                    )
                  }
                >
                  <option disabled value="">
                    --Select a Channel--
                  </option>
                  <For each={payload()?.guild.channels}>
                    {(channel) => (
                      <option value={channel.id}>{channel.name}</option>
                    )}
                  </For>
                </select>
                <Show
                  when={
                    config.features.timePlanning.enabled &&
                    !config.features.timePlanning.channelId
                  }
                >
                  {"<-- or changes won't be saved"}
                </Show>
              </div>
              <div class="flex-row">
                <label>Target Interval:</label>
                <select
                  ref={(e) => setTargetDayRef(e)}
                  value={
                    splitInterval(payload()?.guild.tpInterval ?? 0).targetDay
                  }
                  onInput={(e) =>
                    setConfig(
                      "features",
                      "timePlanning",
                      "targetDay",
                      Number(e.target.value),
                    )
                  }
                >
                  <Index each={Array.from(Array(7)).map((_e, i) => i)}>
                    {(id) => {
                      const weekdays = [
                        "Sunday",
                        "Monday",
                        "Tuesday",
                        "Wednesday",
                        "Thursday",
                        "Friday",
                        "Saturday",
                      ];
                      return (
                        <option value={String(id())}>{weekdays[id()]}</option>
                      );
                    }}
                  </Index>
                </select>
                at
                <select
                  ref={(e) => setTargetHourRef(e)}
                  value={
                    splitInterval(payload()?.guild.tpInterval ?? 0).targetHour
                  }
                  onInput={(e) =>
                    setConfig(
                      "features",
                      "timePlanning",
                      "targetHour",
                      Number(e.target.value),
                    )
                  }
                >
                  <Index each={Array.from(Array(24)).map((_e, i) => i)}>
                    {(id) => (
                      <option value={String(id())}>{String(id())}</option>
                    )}
                  </Index>
                </select>
                :
                <select
                  ref={(e) => setTargetMinuteRef(e)}
                  value={
                    splitInterval(payload()?.guild.tpInterval ?? 0).targetMinute
                  }
                  onInput={(e) =>
                    setConfig(
                      "features",
                      "timePlanning",
                      "targetMinute",
                      Number(e.target.value),
                    )
                  }
                >
                  <Index each={Array.from(Array(60)).map((_e, i) => i)}>
                    {(id) => (
                      <option value={String(id())}>
                        {String(id()).padStart(2, "0")}
                      </option>
                    )}
                  </Index>
                </select>
              </div>
              <div class="flex-row">
                <label for="pingableRoles" class="flex-row">
                  <p>Enable pingable Roles:</p>
                  <FontAwesomeIcon
                    icon={
                      config.features.timePlanning.pingableRoles
                        ? faToggleOn
                        : faToggleOff
                    }
                    size="xl"
                  />
                </label>
                <input
                  hidden
                  type="checkbox"
                  id="pingableRoles"
                  ref={(e) => setPingableRolesRef(e)}
                  checked={payload()?.guild.pingableRoles}
                  onInput={(e) =>
                    setConfig(
                      "features",
                      "timePlanning",
                      "pingableRoles",
                      e.target.checked,
                    )
                  }
                />
              </div>
            </div>
          </section>

          <section class="box">
            <button
              onClick={async () => {
                const id = payload()?.guild.id;
                if (!id || !willUpdateValues()) return;
                await saveConfig(id, updateValues());
                refetch();
              }}
            >
              Apply
            </button>
            <button onClick={() => navigator("/config")}>Back</button>
            <button onClick={() => refetch()}>Reset</button>
            <Show when={willUpdateValues()}>UNSAVED CHANGES</Show>
          </section>
        </div>
      </div>
    </Layout>
  );
}

export default config;
