import type { ActionFunctionArgs, LoaderFunctionArgs } from "react-router";
import { Form, useActionData, useLoaderData, useNavigation } from "react-router";
import { authenticate } from "../shopify.server";
import db from "../db.server";
import {
  isValidAtVatId,
  isValidBic,
  isValidIban,
  normaliseIban,
} from "../services/invoice/validation";
import { STORED_LOGO_SENTINEL } from "../services/invoice/logoCache.constants";
import {
  deleteStoredLogo,
  storeUploadedLogo,
} from "../services/invoice/logoCache.server";
import { RichTextEditor } from "../components/RichTextEditor";
import {
  DEFAULT_EMAIL_BODY_DE,
  DEFAULT_EMAIL_BODY_EN,
  DEFAULT_EMAIL_SUBJECT_DE,
  DEFAULT_EMAIL_SUBJECT_EN,
} from "../services/invoice/emailTemplates";

interface SettingsFieldErrors {
  vatId?: string;
  iban?: string;
  bic?: string;
  smtpPort?: string;
  paymentTermDays?: string;
  invoiceSeed?: string;
  logo?: string;
}

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { session } = await authenticate.admin(request);
  const settings = await db.shopSettings.upsert({
    where: { shopDomain: session.shop },
    update: {},
    create: { shopDomain: session.shop },
  });
  let logoPreviewDataUrl: string | null = null;
  if (settings.logoUrl === STORED_LOGO_SENTINEL) {
    const cached = await db.logoCache.findUnique({ where: { shopDomain: session.shop } });
    if (cached) {
      logoPreviewDataUrl = `data:${cached.contentType};base64,${Buffer.from(cached.bytes).toString("base64")}`;
    }
  } else if (settings.logoUrl) {
    // External HTTPS URL — fine to display directly in the editor.
    logoPreviewDataUrl = settings.logoUrl;
  }
  return { settings, logoPreviewDataUrl };
};

export const action = async ({ request }: ActionFunctionArgs) => {
  const { session } = await authenticate.admin(request);
  const form = await request.formData();
  const errors: SettingsFieldErrors = {};

  const str = (k: string, fallback = "") => (form.get(k) ?? fallback).toString().trim();
  const bool = (k: string) => form.get(k) === "on";
  const intOrNull = (k: string): number | null => {
    const raw = form.get(k);
    if (raw === null || raw === "") return null;
    const n = parseInt(raw.toString(), 10);
    return Number.isFinite(n) ? n : null;
  };

  const vatId = str("vatId").toUpperCase();
  if (vatId && !isValidAtVatId(vatId)) {
    errors.vatId = "Expected format: ATU followed by 8 digits (e.g. ATU12345678).";
  }

  const iban = normaliseIban(str("iban"));
  if (iban && !isValidIban(iban)) {
    errors.iban = "Invalid IBAN (failed checksum or unknown country length).";
  }

  const bic = str("bic").toUpperCase();
  if (bic && !isValidBic(bic)) {
    errors.bic = "Invalid BIC (8 or 11 alphanumeric characters).";
  }

  const smtpPort = intOrNull("smtpPort");
  if (smtpPort !== null && (smtpPort < 1 || smtpPort > 65535)) {
    errors.smtpPort = "Port must be between 1 and 65535.";
  }

  const paymentTermDays = intOrNull("paymentTermDays");
  if (paymentTermDays !== null && (paymentTermDays < 0 || paymentTermDays > 365)) {
    errors.paymentTermDays = "Must be between 0 and 365.";
  }

  const invoiceSeed = intOrNull("invoiceSeed");
  if (invoiceSeed !== null && invoiceSeed < 0) {
    errors.invoiceSeed = "Must be a non-negative number.";
  }

  // --- Logo handling --------------------------------------------------------
  // The settings page allows three actions on the logo:
  //   1. Upload a new file (multipart `logoFile`) — stored in `LogoCache`.
  //   2. Remove the current logo (`removeLogo=on`).
  //   3. Provide an external URL via the `logoUrl` field.
  // If a file is uploaded it wins over a manually-entered URL.
  // Look up the existing logoUrl so we don't accidentally clear it when
  // the user just edited unrelated fields (the visible URL field is hidden
  // for stored uploads, so it submits empty in that case).
  const existing = await db.shopSettings.findUnique({
    where: { shopDomain: session.shop },
    select: { logoUrl: true },
  });
  const submittedLogoUrl = str("logoUrl");
  const removeLogo = bool("removeLogo");
  const logoFile = form.get("logoFile");
  const hasUpload =
    logoFile && typeof logoFile === "object" && "size" in logoFile && (logoFile as File).size > 0;

  let resolvedLogoUrl = submittedLogoUrl || existing?.logoUrl || "";

  if (removeLogo && !hasUpload) {
    await deleteStoredLogo(session.shop);
    resolvedLogoUrl = "";
  } else if (hasUpload) {
    const file = logoFile as File;
    const buf = Buffer.from(await file.arrayBuffer());
    const stored = await storeUploadedLogo(session.shop, buf, file.type);
    if (!stored.ok) {
      errors.logo = stored.error ?? "Failed to store uploaded logo.";
    } else {
      resolvedLogoUrl = STORED_LOGO_SENTINEL;
    }
  }

  if (Object.keys(errors).length > 0) {
    return { ok: false, errors, savedAt: null as string | null };
  }

  const data = {
    companyName: str("companyName"),
    legalForm: str("legalForm"),
    ownerName: str("ownerName"),
    addressLine1: str("addressLine1"),
    addressLine2: str("addressLine2"),
    postalCode: str("postalCode"),
    city: str("city"),
    countryCode: str("countryCode", "AT").toUpperCase(),
    phone: str("phone"),
    email: str("email"),
    website: str("website"),
    vatId,
    taxNumber: str("taxNumber"),
    registrationNo: str("registrationNo"),
    registrationCourt: str("registrationCourt"),
    bankName: str("bankName"),
    iban,
    bic,
    giroCodeEnabled: bool("giroCodeEnabled"),
    numberingMode: str("numberingMode", "shopify_order_number"),
    invoicePrefix: str("invoicePrefix"),
    invoiceSeed: invoiceSeed ?? 1000,
    defaultLanguage: str("defaultLanguage", "de") === "en" ? "en" : "de",
    paymentTermDays: paymentTermDays ?? 14,
    footerNote: str("footerNote"),
    footerNoteEn: str("footerNoteEn"),
    kleinunternehmer: bool("kleinunternehmer"),
    logoUrl: resolvedLogoUrl,
    smtpHost: str("smtpHost"),
    smtpPort: smtpPort ?? 587,
    smtpSecure: bool("smtpSecure"),
    smtpUser: str("smtpUser"),
    smtpPassword: str("smtpPassword"),
    smtpFromName: str("smtpFromName"),
    smtpFromEmail: str("smtpFromEmail"),
    smtpReplyTo: str("smtpReplyTo"),
    emailSubjectDe: str("emailSubjectDe"),
    emailBodyHtmlDe: str("emailBodyHtmlDe"),
    emailSubjectEn: str("emailSubjectEn"),
    emailBodyHtmlEn: str("emailBodyHtmlEn"),
  };

  await db.shopSettings.upsert({
    where: { shopDomain: session.shop },
    update: data,
    create: { shopDomain: session.shop, ...data },
  });

  return { ok: true, errors: {}, savedAt: new Date().toISOString() };
};

export default function SettingsRoute() {
  const { settings, logoPreviewDataUrl } = useLoaderData<typeof loader>();
  const actionData = useActionData<typeof action>();
  const nav = useNavigation();
  const isSaving = nav.state === "submitting";
  const errors = actionData?.errors ?? {};
  const hasStoredLogo = settings.logoUrl === STORED_LOGO_SENTINEL;
  // Show the URL field only when not using a stored upload — keeps the UI
  // simpler and avoids the sentinel value leaking into the input.
  const visibleLogoUrl = hasStoredLogo ? "" : settings.logoUrl;

  return (
    <s-page heading="Invoice settings">
      <s-section>
        <s-paragraph>
          Configure issuer details, bank, numbering, language and SMTP. These
          values are written into every PDF invoice this app generates and must
          match your legal records.
        </s-paragraph>
      </s-section>

      <Form method="post" encType="multipart/form-data">
        <s-section heading="Company">
          <s-stack direction="block" gap="base">
            <Field label="Company name" name="companyName" defaultValue={settings.companyName} />
            <Field label="Legal form (e.g. e.U., GmbH)" name="legalForm" defaultValue={settings.legalForm} />
            <Field label="Owner / managing director" name="ownerName" defaultValue={settings.ownerName} />
            <Field label="Address line 1" name="addressLine1" defaultValue={settings.addressLine1} />
            <Field label="Address line 2" name="addressLine2" defaultValue={settings.addressLine2} />
            <s-stack direction="inline" gap="base">
              <Field label="Postal code" name="postalCode" defaultValue={settings.postalCode} />
              <Field label="City" name="city" defaultValue={settings.city} />
              <Field label="Country (ISO)" name="countryCode" defaultValue={settings.countryCode} />
            </s-stack>
            <s-stack direction="inline" gap="base">
              <Field label="Phone" name="phone" defaultValue={settings.phone} />
              <Field label="E-mail" name="email" defaultValue={settings.email} />
              <Field label="Website" name="website" defaultValue={settings.website} />
            </s-stack>
          </s-stack>
        </s-section>

        <s-section heading="Legal identifiers">
          <s-stack direction="block" gap="base">
            <Field
              label="VAT ID (UID)"
              name="vatId"
              defaultValue={settings.vatId}
              error={errors.vatId}
              helpText="Austrian format: ATU followed by 8 digits."
            />
            <Field label="Tax number (Steuernummer)" name="taxNumber" defaultValue={settings.taxNumber} />
            <Field label="Commercial register no. (FN)" name="registrationNo" defaultValue={settings.registrationNo} />
            <Field label="Commercial register court" name="registrationCourt" defaultValue={settings.registrationCourt} />
            <Toggle
              label="I am a Kleinunternehmer (no VAT charged, § 6 Abs. 1 Z 27 UStG)"
              name="kleinunternehmer"
              checked={settings.kleinunternehmer}
            />
          </s-stack>
        </s-section>

        <s-section heading="Bank">
          <s-stack direction="block" gap="base">
            <Field label="Bank name" name="bankName" defaultValue={settings.bankName} />
            <Field
              label="IBAN"
              name="iban"
              defaultValue={settings.iban}
              error={errors.iban}
            />
            <Field
              label="BIC"
              name="bic"
              defaultValue={settings.bic}
              error={errors.bic}
            />
            <Toggle
              label="Render GiroCode (EPC QR) on unpaid invoices"
              name="giroCodeEnabled"
              checked={settings.giroCodeEnabled}
            />
          </s-stack>
        </s-section>

        <s-section heading="Invoice numbering">
          <s-stack direction="block" gap="base">
            <s-paragraph>
              <strong>shopify_order_number</strong> reuses the Shopify order
              number (e.g. <code>RE-1004</code>). <strong>prefix_sequential</strong>
              {" "}allocates a strictly gapless number from an internal counter
              (legally safest under § 11 UStG).
            </s-paragraph>
            <Select
              label="Numbering mode"
              name="numberingMode"
              defaultValue={settings.numberingMode}
              options={[
                { value: "shopify_order_number", label: "Use Shopify order number" },
                { value: "prefix_sequential", label: "Sequential (gapless, app-managed)" },
              ]}
            />
            <Field label="Invoice number prefix" name="invoicePrefix" defaultValue={settings.invoicePrefix} />
            <Field
              label="Sequential start (last issued; next = this + 1)"
              name="invoiceSeed"
              type="number"
              defaultValue={String(settings.invoiceSeed)}
              error={errors.invoiceSeed}
            />
            <Field
              label="Payment term (days)"
              name="paymentTermDays"
              type="number"
              defaultValue={String(settings.paymentTermDays)}
              error={errors.paymentTermDays}
            />
            <Select
              label="Default language"
              name="defaultLanguage"
              defaultValue={settings.defaultLanguage}
              options={[
                { value: "de", label: "German (de)" },
                { value: "en", label: "English (en)" },
              ]}
            />
            <Field label="Footer note (German)" name="footerNote" defaultValue={settings.footerNote} helpText="Shown at the bottom of every German PDF invoice (e.g. „Vielen Dank für Ihren Auftrag.“)." />
            <Field label="Footer note (English)" name="footerNoteEn" defaultValue={settings.footerNoteEn} helpText="Shown on English PDF invoices. Falls back to the German note when empty." />
          </s-stack>
        </s-section>

        <s-section heading="Logo">
          <s-stack direction="block" gap="base">
            <s-paragraph>
              The logo is rendered in the top-right corner of every invoice
              PDF. Upload an image (PNG, JPEG, WebP or GIF, max 5 MB) or
              provide a publicly reachable HTTPS URL — uploads take precedence.
            </s-paragraph>
            {errors.logo && <s-banner tone="critical">{errors.logo}</s-banner>}
            {logoPreviewDataUrl && (
              <s-stack direction="block" gap="small">
                <s-text>Current logo:</s-text>
                <img
                  src={logoPreviewDataUrl}
                  alt="Current invoice logo"
                  style={{ maxWidth: "240px", maxHeight: "120px", border: "1px solid #ddd", padding: "8px", background: "#fff" }}
                />
                <s-checkbox name="removeLogo" label="Remove current logo on save" />
              </s-stack>
            )}
            <s-text>Upload a new logo:</s-text>
            <input type="file" name="logoFile" accept="image/png,image/jpeg,image/webp,image/gif" />
            <Field
              label="Or external logo URL (PNG/JPG/WebP, served from Shopify Files or any HTTPS URL)"
              name="logoUrl"
              defaultValue={visibleLogoUrl}
            />
          </s-stack>
        </s-section>

        <s-section heading="SMTP (used by the email Flow action)">
          <s-stack direction="block" gap="base">
            <s-stack direction="inline" gap="base">
              <Field label="Host" name="smtpHost" defaultValue={settings.smtpHost} />
              <Field
                label="Port"
                name="smtpPort"
                type="number"
                defaultValue={String(settings.smtpPort)}
                error={errors.smtpPort}
              />
            </s-stack>
            <Toggle
              label="Use TLS (SMTPS) — typically required for port 465"
              name="smtpSecure"
              checked={settings.smtpSecure}
            />
            <Field label="Username" name="smtpUser" defaultValue={settings.smtpUser} />
            <Field label="Password" name="smtpPassword" type="password" defaultValue={settings.smtpPassword} />
            <s-stack direction="inline" gap="base">
              <Field label="From name" name="smtpFromName" defaultValue={settings.smtpFromName} />
              <Field label="From e-mail" name="smtpFromEmail" defaultValue={settings.smtpFromEmail} />
              <Field label="Reply-to" name="smtpReplyTo" defaultValue={settings.smtpReplyTo} />
            </s-stack>
          </s-stack>
        </s-section>

        <s-section heading="Email templates">
          <s-stack direction="block" gap="base">
            <s-paragraph>
              These templates are used when sending the invoice PDF by email.
              Leave a field empty to fall back to the built-in default.
            </s-paragraph>

            <Field
              label="Subject (German)"
              name="emailSubjectDe"
              defaultValue={settings.emailSubjectDe || DEFAULT_EMAIL_SUBJECT_DE}
              helpText="Variables like {{invoiceNumber}} are substituted at send time."
            />
            <RichTextEditor
              name="emailBodyHtmlDe"
              label="Body (German)"
              defaultValue={settings.emailBodyHtmlDe || DEFAULT_EMAIL_BODY_DE}
              variables={EMAIL_VARS}
              minHeight={220}
              logoDataUrl={logoPreviewDataUrl}
            />

            <Field
              label="Subject (English)"
              name="emailSubjectEn"
              defaultValue={settings.emailSubjectEn || DEFAULT_EMAIL_SUBJECT_EN}
              helpText="Variables like {{invoiceNumber}} are substituted at send time."
            />
            <RichTextEditor
              name="emailBodyHtmlEn"
              label="Body (English)"
              defaultValue={settings.emailBodyHtmlEn || DEFAULT_EMAIL_BODY_EN}
              variables={EMAIL_VARS}
              minHeight={220}
              logoDataUrl={logoPreviewDataUrl}
            />
          </s-stack>
        </s-section>

        <s-section>
          <s-stack direction="block" gap="base">
            {actionData?.ok && (
              <s-banner tone="success" heading="Settings saved">
                Your changes are now live and will be used for the next invoice.
              </s-banner>
            )}
            {actionData && !actionData.ok && (
              <s-banner tone="critical" heading="Please fix the highlighted errors">
                Some fields below need attention before settings can be saved.
              </s-banner>
            )}
            <s-stack direction="inline" gap="base" justifyContent="end" alignItems="center">
              {isSaving ? <s-text tone="neutral">Saving…</s-text> : null}
              <s-button type="submit" variant="primary" {...(isSaving ? { loading: true } : {})}>
                Save settings
              </s-button>
            </s-stack>
          </s-stack>
        </s-section>
      </Form>
    </s-page>
  );
}

const EMAIL_VARS = [
  { token: "{{invoiceNumber}}" },
  { token: "{{customerName}}" },
  { token: "{{customerFirstName}}" },
  { token: "{{orderName}}" },
  { token: "{{totalGross}}" },
  { token: "{{dueDate}}" },
  { token: "{{companyName}}" },
  { token: "{{ownerName}}" },
  { token: "{{shopEmail}}" },
  { token: "{{shopWebsite}}" },
];

interface FieldProps {
  label: string;
  name: string;
  defaultValue?: string;
  type?: string;
  error?: string;
  helpText?: string;
}

function Field({ label, name, defaultValue = "", type = "text", error, helpText }: FieldProps) {
  // Polaris web-components don't expose a generic html `type` prop; we use
  // distinct tags only when needed (e.g. password). The form action parses
  // numeric strings server-side.
  if (type === "password") {
    return (
      // s-password-field renders an obscured input
      <s-password-field
        label={label}
        name={name}
        value={defaultValue}
        {...(error ? { error } : {})}
        {...(helpText ? { details: helpText } : {})}
      />
    );
  }
  return (
    <s-text-field
      label={label}
      name={name}
      value={defaultValue}
      {...(error ? { error } : {})}
      {...(helpText ? { details: helpText } : {})}
    />
  );
}

interface ToggleProps { label: string; name: string; checked: boolean }
function Toggle({ label, name, checked }: ToggleProps) {
  return (
    <s-checkbox
      name={name}
      label={label}
      {...(checked ? { checked: true } : {})}
    />
  );
}

interface SelectProps {
  label: string;
  name: string;
  defaultValue: string;
  options: { value: string; label: string }[];
}
function Select({ label, name, defaultValue, options }: SelectProps) {
  return (
    <s-select label={label} name={name} value={defaultValue}>
      {options.map((o) => (
        <s-option key={o.value} value={o.value}>
          {o.label}
        </s-option>
      ))}
    </s-select>
  );
}
