<script setup>
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from "vue";
import {
  Bell,
  BellFilled,
  CircleCheck,
  CopyDocument,
  Delete,
  Download,
  MessageBox,
  Plus,
  Refresh,
  Tickets,
  Wallet,
} from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus";
import DOMPurify from "dompurify";
import { useI18n } from "vue-i18n";
import { apiRequest, websocketUrl } from "@/lib/api";
import {
  getMailboxToken,
  removeMailboxToken,
  setMailboxToken,
} from "@/lib/mailbox-tokens";
import { useAuthStore } from "@/stores/auth";

const auth = useAuthStore();
const { locale, t } = useI18n();
const configuredDomain = import.meta.env.VITE_APP_DOMAIN || "mail.shenjianl.cn";

const loading = ref(true);
const mailboxLoading = ref(false);
const messageLoading = ref(false);
const detailLoading = ref(false);
const mailboxes = ref([]);
const selectedMailboxId = ref(null);
const messages = ref([]);
const selectedMessage = ref(null);
const messageDetail = ref(null);
const hasMoreMessages = ref(false);
const createDialogOpen = ref(false);
const creditsDrawerOpen = ref(false);
const transactions = ref([]);
const notificationEnabled = ref(
  typeof Notification !== "undefined" && Notification.permission === "granted",
);
const createForm = reactive({
  local_part: "",
  note: "",
  ttl_hours: 24,
});
const credit = ref({
  enabled: false,
  balance: 0,
  total_granted: 0,
  total_consumed: 0,
  pricing: { create_mailbox: 0, receive_email: 0 },
  daily_limits: { mailboxes: 0, emails: 0 },
  usage_today: { mailboxes_created: 0, emails_received: 0, bytes_received: 0 },
  remaining_today: { mailboxes: 0, emails: 0 },
  check_in: {
    claimed: false,
    available: false,
    reward: 0,
    reward_granted: null,
    reward_balance_cap: 0,
    date: "",
  },
});

let socket = null;
let reconnectTimer = null;

const selectedMailbox = computed(() =>
  mailboxes.value.find((mailbox) => mailbox.id === selectedMailboxId.value),
);

const sanitizedBody = computed(() => {
  if (!messageDetail.value) return "";
  if (messageDetail.value.html_body) {
    return DOMPurify.sanitize(messageDetail.value.html_body, {
      FORBID_TAGS: [
        "script",
        "style",
        "iframe",
        "object",
        "embed",
        "form",
        "img",
        "svg",
        "math",
      ],
      FORBID_ATTR: ["style"],
    });
  }
  const text = messageDetail.value.text_body || t("mail.noBody");
  return `<pre>${escapeHtml(text)}</pre>`;
});

const mailboxUsagePercent = computed(() => {
  const mailbox = selectedMailbox.value;
  if (!mailbox?.max_quota_bytes) return 0;
  return Math.min(
    100,
    Math.round((mailbox.used_bytes / mailbox.max_quota_bytes) * 100),
  );
});

const canCreateMailbox = computed(
  () =>
    credit.value.remaining_today.mailboxes > 0 &&
    credit.value.balance >= credit.value.pricing.create_mailbox,
);

function escapeHtml(value) {
  return String(value)
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;")
    .replaceAll("'", "&#039;");
}

function formatDate(value) {
  if (!value) return "—";
  return new Intl.DateTimeFormat(locale.value === "zh" ? "zh-CN" : "en-US", {
    month: "short",
    day: "numeric",
    hour: "2-digit",
    minute: "2-digit",
  }).format(new Date(value));
}

function formatBytes(value = 0) {
  if (value < 1024) return `${value} B`;
  if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
  return `${(value / 1024 / 1024).toFixed(1)} MB`;
}

function transactionReason(reason) {
  const key = `credits.reasons.${reason}`;
  const translated = t(key);
  return translated === key ? reason : translated;
}

async function loadDashboard() {
  loading.value = true;
  try {
    await Promise.all([loadCredit(), loadMailboxes()]);
  } catch (error) {
    if (error.status === 401) await auth.logout();
    ElMessage.error(error.message || t("common.requestFailed"));
  } finally {
    loading.value = false;
  }
}

async function loadCredit() {
  credit.value = await apiRequest("/api/credits");
}

async function loadTransactions() {
  transactions.value = await apiRequest(
    "/api/credits/transactions?limit=100&offset=0",
  );
}

async function showTransactions() {
  creditsDrawerOpen.value = true;
  try {
    await loadTransactions();
  } catch (error) {
    ElMessage.error(error.message || t("common.requestFailed"));
  }
}

async function checkIn() {
  try {
    const result = await apiRequest("/api/credits/check-in", {
      method: "POST",
    });
    await Promise.all([loadCredit(), loadTransactions()]);
    if (result.claimed) {
      ElMessage.success(
        t("credits.checkInSuccess", { reward: result.reward_granted }),
      );
    } else {
      ElMessage.info(t("credits.checkedIn"));
    }
  } catch (error) {
    ElMessage.error(error.message || t("credits.checkInFailed"));
  }
}

async function loadMailboxes() {
  mailboxLoading.value = true;
  try {
    mailboxes.value = await apiRequest("/api/mailboxes");
    const active = mailboxes.value.find(
      (mailbox) => mailbox.status === "active",
    );
    if (active && !selectedMailboxId.value) {
      await selectMailbox(active);
    }
  } finally {
    mailboxLoading.value = false;
  }
}

async function ensureMailboxToken(mailbox, forceRotate = false) {
  let token = forceRotate ? null : getMailboxToken(auth.email, mailbox.id);
  if (!token) {
    const result = await apiRequest(
      `/api/mailboxes/${mailbox.id}/rotate-token`,
      {
        method: "POST",
      },
    );
    token = result.access_token;
    setMailboxToken(auth.email, mailbox.id, token);
  }
  return token;
}

async function mailboxRequest(mailbox, path, options = {}, canRetry = true) {
  const token = await ensureMailboxToken(mailbox);
  try {
    return await apiRequest(path, {
      ...options,
      auth: "mailbox",
      mailboxToken: token,
    });
  } catch (error) {
    if (error.status === 401 && canRetry) {
      removeMailboxToken(auth.email, mailbox.id);
      const nextToken = await ensureMailboxToken(mailbox, true);
      return apiRequest(path, {
        ...options,
        auth: "mailbox",
        mailboxToken: nextToken,
      });
    }
    throw error;
  }
}

async function selectMailbox(mailbox) {
  if (mailbox.status !== "active") {
    ElMessage.warning(t(`mailbox.status.${mailbox.status}`));
    return;
  }
  selectedMailboxId.value = mailbox.id;
  selectedMessage.value = null;
  messageDetail.value = null;
  messages.value = [];
  closeSocket();
  await fetchMessages(true);
  await openSocket(mailbox);
}

async function fetchMessages(reset = true) {
  const mailbox = selectedMailbox.value;
  if (!mailbox) return;
  messageLoading.value = true;
  try {
    const offset = reset ? 0 : messages.value.length;
    const result = await mailboxRequest(
      mailbox,
      `/api/mailboxes/${mailbox.id}/emails?limit=50&offset=${offset}`,
    );
    messages.value = reset ? result : [...messages.value, ...result];
    hasMoreMessages.value = result.length === 50;
    if (reset && result.length > 0) {
      await selectMessage(result[0]);
    }
  } catch (error) {
    ElMessage.error(error.message || t("mail.fetchFailed"));
  } finally {
    messageLoading.value = false;
  }
}

async function selectMessage(message) {
  const mailbox = selectedMailbox.value;
  if (!mailbox) return;
  selectedMessage.value = message;
  detailLoading.value = true;
  try {
    messageDetail.value = await mailboxRequest(
      mailbox,
      `/api/mailboxes/${mailbox.id}/emails/${message.id}`,
    );
  } catch (error) {
    ElMessage.error(error.message || t("mail.fetchDetailFailed"));
  } finally {
    detailLoading.value = false;
  }
}

async function createMailbox() {
  try {
    const body = {
      local_part: createForm.local_part || null,
      note: createForm.note || null,
      ttl_hours: createForm.ttl_hours,
    };
    if (configuredDomain) body.domain = configuredDomain;
    const created = await apiRequest("/api/mailboxes", {
      method: "POST",
      body,
    });
    setMailboxToken(auth.email, created.id, created.access_token);
    mailboxes.value.unshift(created);
    createDialogOpen.value = false;
    createForm.local_part = "";
    createForm.note = "";
    await Promise.all([
      selectMailbox(created),
      loadCredit(),
      loadTransactions(),
    ]);
    ElMessage.success(t("mailbox.created"));
  } catch (error) {
    ElMessage.error(error.message || t("mailbox.createFailed"));
  }
}

async function revokeMailbox(mailbox) {
  try {
    await ElMessageBox.confirm(
      t("mailbox.revokeConfirm", { address: mailbox.address }),
      t("mailbox.revoke"),
      { type: "warning" },
    );
    await apiRequest(`/api/mailboxes/${mailbox.id}`, { method: "DELETE" });
    removeMailboxToken(auth.email, mailbox.id);
    if (selectedMailboxId.value === mailbox.id) {
      selectedMailboxId.value = null;
      messages.value = [];
      messageDetail.value = null;
      closeSocket();
    }
    await loadMailboxes();
    ElMessage.success(t("mailbox.revoked"));
  } catch (error) {
    if (error !== "cancel" && error !== "close") {
      ElMessage.error(error.message || t("mailbox.revokeFailed"));
    }
  }
}

async function deleteMessage() {
  const mailbox = selectedMailbox.value;
  const message = messageDetail.value;
  if (!mailbox || !message) return;
  try {
    await ElMessageBox.confirm(t("mail.deleteConfirm"), t("mail.delete"), {
      type: "warning",
    });
    await mailboxRequest(
      mailbox,
      `/api/mailboxes/${mailbox.id}/emails/${message.id}`,
      { method: "DELETE" },
    );
    messages.value = messages.value.filter((item) => item.id !== message.id);
    selectedMessage.value = null;
    messageDetail.value = null;
    const next = messages.value[0];
    if (next) await selectMessage(next);
    await loadMailboxes();
    ElMessage.success(t("mail.deleted"));
  } catch (error) {
    if (error !== "cancel" && error !== "close") {
      ElMessage.error(error.message || t("mail.deleteFailed"));
    }
  }
}

async function downloadAttachment(attachment) {
  const mailbox = selectedMailbox.value;
  const message = messageDetail.value;
  if (!mailbox || !message) return;
  try {
    const result = await mailboxRequest(
      mailbox,
      `/api/mailboxes/${mailbox.id}/emails/${message.id}/attachments/${attachment.id}`,
      { responseType: "blob" },
    );
    const url = URL.createObjectURL(result.blob);
    const anchor = document.createElement("a");
    anchor.href = url;
    anchor.download = result.filename || attachment.filename || "attachment";
    anchor.click();
    URL.revokeObjectURL(url);
  } catch (error) {
    ElMessage.error(error.message || t("mail.downloadFailed"));
  }
}

async function copyAddress(address) {
  try {
    await navigator.clipboard.writeText(address);
    ElMessage.success(t("common.copied"));
  } catch {
    ElMessage.error(t("common.copyFailed"));
  }
}

async function enableNotifications() {
  if (typeof Notification === "undefined") {
    ElMessage.warning(t("mail.notificationsUnsupported"));
    return;
  }
  const permission = await Notification.requestPermission();
  notificationEnabled.value = permission === "granted";
  ElMessage[notificationEnabled.value ? "success" : "warning"](
    notificationEnabled.value
      ? t("mail.notificationsEnabled")
      : t("mail.notificationsDenied"),
  );
}

function scheduleReconnect(mailbox) {
  if (selectedMailboxId.value !== mailbox.id || reconnectTimer) return;
  reconnectTimer = window.setTimeout(async () => {
    reconnectTimer = null;
    if (selectedMailboxId.value !== mailbox.id) return;
    try {
      await openSocket(mailbox);
    } catch {
      scheduleReconnect(mailbox);
    }
  }, 3000);
}

async function openSocket(mailbox) {
  const token = await ensureMailboxToken(mailbox);
  const url = websocketUrl(
    `/api/mailboxes/${mailbox.id}/ws?token=${encodeURIComponent(token)}`,
  );
  socket = new WebSocket(url);
  socket.onmessage = async (event) => {
    try {
      const payload = JSON.parse(event.data);
      if (payload.event && payload.event !== "new_email") return;
      const incoming = payload.data || payload;
      if (!incoming?.id) return;
      if (!messages.value.some((message) => message.id === incoming.id)) {
        messages.value.unshift(incoming);
      }
      ElMessage.success(t("mail.newMail"));
      if (notificationEnabled.value && document.hidden) {
        new Notification(incoming.subject || t("mail.noSubject"), {
          body: incoming.mail_from,
        });
      }
      await Promise.all([loadCredit(), loadMailboxes()]);
    } catch (error) {
      console.warn("Failed to handle mailbox event", error);
    }
  };
  socket.onclose = () => {
    scheduleReconnect(mailbox);
  };
}

function closeSocket() {
  if (reconnectTimer) {
    window.clearTimeout(reconnectTimer);
    reconnectTimer = null;
  }
  if (socket) {
    socket.onclose = null;
    socket.close();
    socket = null;
  }
}

onMounted(async () => {
  await nextTick();
  await loadDashboard();
});

onUnmounted(closeSocket);
</script>

<template>
  <main class="workspace-shell" :aria-busy="loading">
    <section class="workspace-summary">
      <div>
        <p class="section-kicker">{{ t("workspace.account") }}</p>
        <h1>{{ t("workspace.title") }}</h1>
      </div>

      <div class="summary-metrics">
        <button class="metric-command" type="button" @click="showTransactions">
          <el-icon><Wallet /></el-icon>
          <span>
            <small>{{ t("credits.balance") }}</small>
            <strong>{{ credit.balance }}</strong>
          </span>
        </button>
        <div class="metric">
          <small>{{ t("credits.mailboxesToday") }}</small>
          <strong>
            {{ credit.usage_today.mailboxes_created }}/{{
              credit.daily_limits.mailboxes
            }}
          </strong>
        </div>
        <div class="metric">
          <small>{{ t("credits.emailsToday") }}</small>
          <strong>
            {{ credit.usage_today.emails_received }}/{{
              credit.daily_limits.emails
            }}
          </strong>
        </div>
      </div>

      <div class="summary-actions">
        <button
          class="icon-command"
          type="button"
          :title="t('mail.enableNotifications')"
          :aria-label="t('mail.enableNotifications')"
          @click="enableNotifications"
        >
          <el-icon
            ><BellFilled v-if="notificationEnabled" /><Bell v-else
          /></el-icon>
        </button>
        <button
          class="primary-command compact"
          type="button"
          :disabled="!canCreateMailbox"
          @click="createDialogOpen = true"
        >
          <el-icon><Plus /></el-icon>
          {{ t("mailbox.create") }}
        </button>
        <button
          class="text-command check-in-command"
          type="button"
          :disabled="credit.check_in.claimed"
          @click="checkIn"
        >
          <el-icon><CircleCheck /></el-icon>
          {{
            credit.check_in.claimed
              ? t("credits.checkedIn")
              : t("credits.checkIn", { reward: credit.check_in.reward })
          }}
        </button>
      </div>
    </section>

    <section v-if="loading" class="workspace-loading">
      {{ t("common.loading") }}
    </section>

    <section v-else class="mail-workspace">
      <aside class="mailbox-rail">
        <div class="panel-heading">
          <div>
            <p class="section-kicker">{{ t("mailbox.label") }}</p>
            <h2>{{ t("mailbox.yours") }}</h2>
          </div>
          <button
            class="icon-command"
            type="button"
            :title="t('common.refresh')"
            :aria-label="t('common.refresh')"
            :disabled="mailboxLoading"
            @click="loadMailboxes"
          >
            <el-icon><Refresh /></el-icon>
          </button>
        </div>

        <div v-if="mailboxes.length === 0" class="empty-panel">
          <el-icon><MessageBox /></el-icon>
          <p>{{ t("mailbox.empty") }}</p>
          <button
            class="text-command"
            type="button"
            :disabled="!canCreateMailbox"
            @click="createDialogOpen = true"
          >
            {{ t("mailbox.createFirst") }}
          </button>
        </div>

        <div v-else class="mailbox-list">
          <button
            v-for="mailbox in mailboxes"
            :key="mailbox.id"
            class="mailbox-row"
            :class="{ selected: mailbox.id === selectedMailboxId }"
            type="button"
            @click="selectMailbox(mailbox)"
          >
            <span class="status-dot" :class="mailbox.status"></span>
            <span class="mailbox-copy">
              <strong>{{ mailbox.local_part }}</strong>
              <small>@{{ mailbox.domain }}</small>
              <small>{{ formatDate(mailbox.expires_at) }}</small>
            </span>
            <span
              class="row-delete"
              role="button"
              tabindex="0"
              :title="t('mailbox.revoke')"
              @click.stop="revokeMailbox(mailbox)"
              @keydown.enter.stop="revokeMailbox(mailbox)"
            >
              <el-icon><Delete /></el-icon>
            </span>
          </button>
        </div>
      </aside>

      <section class="message-index">
        <div class="panel-heading">
          <div>
            <p class="section-kicker">
              {{ selectedMailbox?.address || t("mailbox.none") }}
            </p>
            <h2>{{ t("mail.inbox") }}</h2>
          </div>
          <div class="panel-actions">
            <button
              v-if="selectedMailbox"
              class="icon-command"
              type="button"
              :title="t('common.copy')"
              :aria-label="t('common.copy')"
              @click="copyAddress(selectedMailbox.address)"
            >
              <el-icon><CopyDocument /></el-icon>
            </button>
            <button
              class="icon-command"
              type="button"
              :title="t('common.refresh')"
              :aria-label="t('common.refresh')"
              :disabled="!selectedMailbox || messageLoading"
              @click="fetchMessages(true)"
            >
              <el-icon><Refresh /></el-icon>
            </button>
          </div>
        </div>

        <div v-if="selectedMailbox" class="quota-line">
          <span>{{ formatBytes(selectedMailbox.used_bytes) }}</span>
          <div><i :style="{ width: `${mailboxUsagePercent}%` }"></i></div>
          <span>{{ formatBytes(selectedMailbox.max_quota_bytes) }}</span>
        </div>
        <p v-if="selectedMailbox" class="billing-note inbox-cost">
          {{
            t("credits.receiveCost", {
              cost: credit.pricing.receive_email,
              remaining: credit.remaining_today.emails,
            })
          }}
        </p>

        <div v-if="!selectedMailbox" class="empty-panel">
          <p>{{ t("mailbox.selectPrompt") }}</p>
        </div>
        <div
          v-else-if="messageLoading && messages.length === 0"
          class="empty-panel"
        >
          <p>{{ t("common.loading") }}</p>
        </div>
        <div v-else-if="messages.length === 0" class="empty-panel">
          <el-icon><MessageBox /></el-icon>
          <p>{{ t("mail.empty") }}</p>
        </div>
        <div v-else class="message-list">
          <button
            v-for="message in messages"
            :key="message.id"
            class="message-row"
            :class="{ selected: selectedMessage?.id === message.id }"
            type="button"
            @click="selectMessage(message)"
          >
            <span class="message-from">
              {{ message.sender_name || message.mail_from }}
            </span>
            <strong>{{ message.subject || t("mail.noSubject") }}</strong>
            <small>{{ formatDate(message.received_at) }}</small>
          </button>
          <button
            v-if="hasMoreMessages"
            class="load-more"
            type="button"
            :disabled="messageLoading"
            @click="fetchMessages(false)"
          >
            {{ t("common.loadMore") }}
          </button>
        </div>
      </section>

      <article class="message-inspector">
        <div v-if="!messageDetail && !detailLoading" class="empty-panel">
          <el-icon><Tickets /></el-icon>
          <p>{{ t("mail.selectPrompt") }}</p>
        </div>
        <div v-else-if="detailLoading" class="empty-panel">
          <p>{{ t("common.loading") }}</p>
        </div>
        <template v-else>
          <header class="message-header">
            <div>
              <p class="section-kicker">
                {{ formatDate(messageDetail.received_at) }}
              </p>
              <h2>{{ messageDetail.subject || t("mail.noSubject") }}</h2>
              <p>
                {{ messageDetail.sender_name || messageDetail.mail_from }}
                <span>&lt;{{ messageDetail.mail_from }}&gt;</span>
              </p>
            </div>
            <button
              class="icon-command danger"
              type="button"
              :title="t('mail.delete')"
              :aria-label="t('mail.delete')"
              @click="deleteMessage"
            >
              <el-icon><Delete /></el-icon>
            </button>
          </header>

          <p v-if="messageDetail.html_body" class="privacy-note">
            {{ t("mail.remoteImagesBlocked") }}
          </p>
          <div class="message-body" v-html="sanitizedBody"></div>

          <section
            v-if="messageDetail.attachments?.length"
            class="attachment-list"
          >
            <h3>{{ t("mail.attachments") }}</h3>
            <button
              v-for="attachment in messageDetail.attachments"
              :key="attachment.id"
              type="button"
              @click="downloadAttachment(attachment)"
            >
              <span>
                <strong>{{
                  attachment.filename || t("mail.unnamedAttachment")
                }}</strong>
                <small
                  >{{ attachment.content_type }} ·
                  {{ formatBytes(attachment.size_bytes) }}</small
                >
              </span>
              <el-icon><Download /></el-icon>
            </button>
          </section>
        </template>
      </article>
    </section>

    <el-dialog
      v-model="createDialogOpen"
      :title="t('mailbox.create')"
      width="min(92vw, 480px)"
    >
      <form class="dialog-form" @submit.prevent="createMailbox">
        <label>
          <span>{{ t("mailbox.localPart") }}</span>
          <div class="address-field">
            <input
              v-model.trim="createForm.local_part"
              maxlength="64"
              pattern="[a-zA-Z0-9._-]+"
              :placeholder="t('mailbox.randomPlaceholder')"
            />
            <span v-if="configuredDomain">@{{ configuredDomain }}</span>
          </div>
        </label>
        <label>
          <span>{{ t("mailbox.note") }}</span>
          <input
            v-model.trim="createForm.note"
            maxlength="128"
            :placeholder="t('mailbox.notePlaceholder')"
          />
        </label>
        <label>
          <span>{{ t("mailbox.ttl") }}</span>
          <el-input-number v-model="createForm.ttl_hours" :min="1" :max="168" />
        </label>
        <p class="billing-note">
          {{ t("mailbox.costNotice", { cost: credit.pricing.create_mailbox }) }}
        </p>
        <button
          class="primary-command"
          type="submit"
          :disabled="!canCreateMailbox"
        >
          <el-icon><Plus /></el-icon>
          {{ t("mailbox.create") }}
        </button>
      </form>
    </el-dialog>

    <el-drawer
      v-model="creditsDrawerOpen"
      :title="t('credits.history')"
      size="min(92vw, 440px)"
    >
      <div class="credit-overview">
        <div>
          <small>{{ t("credits.balance") }}</small>
          <strong>{{ credit.balance }}</strong>
        </div>
        <p>
          {{
            t("credits.summary", {
              granted: credit.total_granted,
              consumed: credit.total_consumed,
            })
          }}
        </p>
      </div>
      <div v-if="transactions.length === 0" class="empty-panel">
        <p>{{ t("credits.empty") }}</p>
      </div>
      <ol v-else class="transaction-list">
        <li v-for="transaction in transactions" :key="transaction.id">
          <span>
            <strong>{{ transactionReason(transaction.reason) }}</strong>
            <small v-if="transaction.description">{{
              transaction.description
            }}</small>
            <small>{{ formatDate(transaction.created_at) }}</small>
          </span>
          <span :class="transaction.delta >= 0 ? 'positive' : 'negative'">
            {{ transaction.delta >= 0 ? "+" : "" }}{{ transaction.delta }}
            <small>{{
              t("credits.balanceAfter", { balance: transaction.balance_after })
            }}</small>
          </span>
        </li>
      </ol>
    </el-drawer>
  </main>
</template>
