"use client";

import type {
  Brand,
  CloudflareAccount,
  CloudflareProvisionStep,
  Domain,
  DomainPurpose,
  Region,
  RegistrarAccount,
  ServerAccount,
  ServerGroup,
} from "@domainops/shared-types";
import { App, Alert, Button, Divider, Form, Input, Modal, Progress, Radio, Select, Steps, Upload } from "antd";
import { CheckCircleFilled, CloseCircleFilled, InboxOutlined, LoadingOutlined, PlusOutlined } from "@ant-design/icons";
import { useEffect, useState } from "react";
import { createDomain, listBrands, listRegions, provisionServer } from "@/lib/api/domains";
import { provisionCloudflare, listCloudflareAccounts } from "@/lib/api/cloudflare";
import { deployWordPress, deployZip } from "@/lib/api/deploy";
import { listServerAccounts, listServerGroups } from "@/lib/api/servers";
import { buildServerOptions, resolveServerAccountId } from "@/lib/utils/serverChoice";
import { listRegistrars } from "@/lib/api/acquire";
import { ApiError } from "@/lib/api/client";
import { PURPOSE_OPTIONS } from "@/lib/utils/domainFields";
import { BrandFormModal } from "./brands/BrandFormModal";

interface AddDomainFormValues {
  project_brand_id?: number;
  fqdn: string;
  brand_id?: number;
  region_id?: number;
  registrar_account_id?: number;
  registrar_label?: string;
  cf_account_id?: number;
  server_account_id?: number | string;
  purpose?: DomainPurpose;
  notes?: string;
}

interface WordPressFormValues {
  admin_user: string;
  admin_password: string;
  admin_email: string;
}

type StepState = "waiting" | "running" | "done" | "failed";

interface SetupTask {
  key: "server" | "cloudflare";
  label: string;
  state: StepState;
  steps: CloudflareProvisionStep[];
  error?: string;
}

const INITIAL_TASKS: SetupTask[] = [
  { key: "server", label: "Server - create the domain and its folder in cPanel", state: "waiting", steps: [] },
  { key: "cloudflare", label: "Cloudflare - DNS records, HTTPS, bot protection, registrar nameservers", state: "waiting", steps: [] },
];

const DOMAIN_PATTERN = /^[a-z0-9-]+(\.[a-z0-9-]+)+$/;

/** One domain per line, or separated by commas, spaces or semicolons; protocols and paths are dropped. */
function parseDomains(text: string): string[] {
  const names = text
    .split(/[\s,;]+/)
    .map((entry) => entry.trim().toLowerCase().replace(/^https?:\/\//, "").replace(/\/.*$/, ""))
    .filter(Boolean);

  return [...new Set(names)];
}

function StateIcon({ state }: { state: StepState }) {
  if (state === "running") return <LoadingOutlined />;
  if (state === "done") return <CheckCircleFilled style={{ color: "#16a34a" }} />;
  if (state === "failed") return <CloseCircleFilled style={{ color: "#dc2626" }} />;

  return <span style={{ display: "inline-block", width: 14, height: 14, borderRadius: "50%", border: "2px solid #c4c8d8" }} />;
}

/**
 * Add Domain in three steps: the details, then (only if asked) the setup -
 * the domain and folder on the server, then Cloudflare - and finally what to
 * put on the site (a ZIP, WordPress, or later). Stopping after the first step
 * just saves the record. The popup cannot be dismissed by clicking outside,
 * so a half-finished setup is never lost by accident.
 */
export function AddDomainModal({
  open,
  onClose,
  onCreated,
}: {
  open: boolean;
  onClose: () => void;
  onCreated: () => void;
}) {
  const { message, modal } = App.useApp();
  const [form] = Form.useForm<AddDomainFormValues>();
  const [wpForm] = Form.useForm<WordPressFormValues>();
  const [brands, setBrands] = useState<Brand[]>([]);
  const [regions, setRegions] = useState<Region[]>([]);
  const [serverAccounts, setServerAccounts] = useState<ServerAccount[]>([]);
  const [serverGroups, setServerGroups] = useState<ServerGroup[]>([]);
  const [registrars, setRegistrars] = useState<RegistrarAccount[]>([]);
  const [cloudflareAccounts, setCloudflareAccounts] = useState<CloudflareAccount[]>([]);
  const [isBrandModalOpen, setIsBrandModalOpen] = useState(false);
  const selectedProjectId = Form.useWatch("brand_id", form);
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [step, setStep] = useState(0);
  const [domain, setDomain] = useState<Domain | null>(null);
  const [tasks, setTasks] = useState<SetupTask[]>(INITIAL_TASKS);
  const [isSettingUp, setIsSettingUp] = useState(false);
  const [contentChoice, setContentChoice] = useState<"zip" | "wordpress" | "later">("later");
  const [uploadPercent, setUploadPercent] = useState<number | null>(null);
  const [isInstalling, setIsInstalling] = useState(false);
  const [contentDone, setContentDone] = useState<string | null>(null);

  useEffect(() => {
    if (!open) return;
    void listBrands().then(setBrands);
    void listRegions().then(setRegions);
    void listServerAccounts().then(setServerAccounts);
    void listServerGroups().then(setServerGroups);
    void listRegistrars().then((r) => setRegistrars(r.accounts.filter((a) => a.is_active)));
    void listCloudflareAccounts().then(setCloudflareAccounts);
  }, [open]);

  function reset() {
    form.resetFields();
    wpForm.resetFields();
    setStep(0);
    setDomain(null);
    setTasks(INITIAL_TASKS);
    setContentChoice("later");
    setUploadPercent(null);
    setContentDone(null);
  }

  function finish(created = domain !== null) {
    reset();
    if (created) onCreated();
    else onClose();
  }

  function handleBrandCreated(brand: Brand) {
    setBrands((current) => [...current, brand]);
    form.setFieldValue("brand_id", brand.id);
    setIsBrandModalOpen(false);
  }

  async function saveDomain(values: AddDomainFormValues, withServer: boolean): Promise<Domain> {
    const { server_account_id: serverChoice, purpose, ...rest } = values;
    const serverAccountId = withServer ? await resolveServerAccountId(serverChoice, rest.fqdn, purpose) : undefined;

    return createDomain({
      ...rest,
      ...(withServer ? { server_account_id: serverAccountId, purpose } : { purpose }),
    });
  }

  async function handleSaveOnly() {
    try {
      const values = await form.validateFields();
      const names = parseDomains(values.fqdn);
      setIsSubmitting(true);

      const failures: string[] = [];
      let added = 0;
      for (const name of names) {
        try {
          const serverAccountId = await resolveServerAccountId(values.server_account_id, name, values.purpose);
          await createDomain({ ...values, fqdn: name, server_account_id: serverAccountId });
          added++;
        } catch (err) {
          failures.push(`${name}: ${err instanceof ApiError ? err.message : "could not be added"}`);
        }
      }

      if (added > 0) message.success(added === 1 ? `${names[0]} added to inventory.` : `${added} domains added to inventory.`);
      if (failures.length > 0) {
        modal.warning({ title: `${failures.length} domain(s) were not added`, content: <ul className="list-disc pl-4">{failures.map((f) => <li key={f}>{f}</li>)}</ul> });
      }
      if (added > 0) finish(true);
    } catch {
      // Field validation messages are already on the form.
    } finally {
      setIsSubmitting(false);
    }
  }

  async function runTask(created: Domain, values: AddDomainFormValues, index: number): Promise<boolean> {
    const key = INITIAL_TASKS[index].key;
    setTasks((current) => current.map((t, i) => (i === index ? { ...t, state: "running", error: undefined } : t)));

    try {
      const steps =
        key === "server"
          ? (await provisionServer(created.id, { server_account_id: (await resolveServerAccountId(values.server_account_id, created.fqdn, values.purpose)) as number, purpose: values.purpose as DomainPurpose })).steps
          : (await provisionCloudflare(created.id)).steps;
      const failed = steps.find((s) => s.status === "failed");

      setTasks((current) =>
        current.map((t, i) => (i === index ? { ...t, state: failed ? "failed" : "done", steps, error: failed?.message } : t)),
      );

      return !failed;
    } catch (err) {
      const text = err instanceof ApiError ? err.message : "That step failed.";
      setTasks((current) => current.map((t, i) => (i === index ? { ...t, state: "failed", error: text } : t)));

      return false;
    }
  }

  async function runSetup(created: Domain, values: AddDomainFormValues, from = 0) {
    setIsSettingUp(true);
    for (let index = from; index < INITIAL_TASKS.length; index++) {
      const ok = await runTask(created, values, index);
      if (!ok) {
        setIsSettingUp(false);

        return;
      }
    }
    setIsSettingUp(false);
    setContentChoice(values.purpose === "wordpress" ? "wordpress" : values.purpose === "html" ? "zip" : "later");
  }

  async function handleProceed() {
    try {
      const values = await form.validateFields();

      if (parseDomains(values.fqdn).length > 1) {
        message.warning("Setup runs one domain at a time - use Save only to add several, then set each one up from its own page.");

        return;
      }

      const missing = [
        !values.server_account_id && "a server account",
        !values.purpose && "a purpose",
        !values.registrar_account_id && "a registrar account",
        !values.cf_account_id && !values.brand_id && "a Cloudflare account (or a project that has one)",
      ].filter(Boolean);

      if (missing.length > 0) {
        message.warning(`To set it up now, pick ${missing.join(", ")}.`);

        return;
      }

      setIsSubmitting(true);
      const created = await saveDomain({ ...values, fqdn: parseDomains(values.fqdn)[0] }, false);
      setDomain(created);
      setStep(1);
      setIsSubmitting(false);
      await runSetup(created, values);
    } catch (err) {
      setIsSubmitting(false);
      if (err instanceof ApiError) message.error(err.message);
    }
  }

  const allSetupDone = tasks.every((t) => t.state === "done");
  const failedIndex = tasks.findIndex((t) => t.state === "failed");

  async function handleZip(file: File) {
    if (!domain) return;
    setUploadPercent(0);
    try {
      const deployment = await deployZip(domain.id, file, setUploadPercent);
      if (deployment.result === "failed") {
        throw new ApiError(deployment.steps.find((s) => s.status === "failed")?.message ?? "The deploy did not finish.", 422, null);
      }
      setContentDone(`${file.name} deployed.`);
    } catch (err) {
      message.error(err instanceof ApiError ? err.message : "Deploy failed.");
    } finally {
      setUploadPercent(null);
    }
  }

  async function handleWordPress(values: WordPressFormValues) {
    if (!domain) return;
    setIsInstalling(true);
    try {
      await deployWordPress(domain.id, values);
      setContentDone("WordPress installed.");
    } catch (err) {
      message.error(err instanceof ApiError ? err.message : "WordPress install failed.");
    } finally {
      setIsInstalling(false);
    }
  }

  const detailsFooter = [
    <Button key="cancel" onClick={() => finish()} className="dms_add_domain_cancel_btn">
      Cancel
    </Button>,
    <Button key="save" onClick={() => void handleSaveOnly()} loading={isSubmitting} className="dms_add_domain_save_only_btn">
      Save only - set up later
    </Button>,
    <Button key="proceed" type="primary" onClick={() => void handleProceed()} loading={isSubmitting} className="dms_add_domain_proceed_btn">
      Proceed to setup
    </Button>,
  ];

  const setupFooter = [
    ...(failedIndex >= 0 && !isSettingUp
      ? [
          <Button
            key="retry"
            onClick={() => domain && void runSetup(domain, form.getFieldsValue(), failedIndex)}
            className="dms_add_domain_retry_btn"
          >
            Retry
          </Button>,
        ]
      : []),
    <Button key="later" onClick={() => finish()} disabled={isSettingUp} className="dms_add_domain_setup_later_btn">
      {allSetupDone ? "Done" : "Finish later"}
    </Button>,
    <Button key="next" type="primary" disabled={!allSetupDone} onClick={() => setStep(2)} className="dms_add_domain_next_btn">
      Next
    </Button>,
  ];

  const contentFooter = [
    <Button key="finish" type="primary" onClick={() => finish()} disabled={uploadPercent !== null || isInstalling} className="dms_add_domain_finish_btn">
      {contentDone || contentChoice === "later" ? "Finish" : "Skip - do it later"}
    </Button>,
  ];

  return (
    <Modal
      title={domain ? `Set up ${domain.fqdn}` : "Add Domain"}
      open={open}
      onCancel={() => finish()}
      mask={{ closable: false }}
      footer={step === 0 ? detailsFooter : step === 1 ? setupFooter : contentFooter}
      width={640}
      className="dms_add_domain_modal"
    >
      <Steps
        size="small"
        current={step}
        items={[{ title: "Details" }, { title: "Setup" }, { title: "Content" }]}
        className="mb-4 dms_add_domain_steps"
      />

      {step === 0 && (
        <Form<AddDomainFormValues> form={form} layout="vertical" className="dms_add_domain_form">
          <Form.Item
            name="fqdn"
            label="Domain name"
            extra="To add several at once, put one domain per line."
            rules={[
              { required: true, message: "Enter the domain name" },
              {
                validator: (_, value: string | undefined) => {
                  const bad = parseDomains(value ?? "").filter((name) => !DOMAIN_PATTERN.test(name));

                  return bad.length === 0 ? Promise.resolve() : Promise.reject(new Error(`Not a valid domain: ${bad.join(", ")}`));
                },
              },
            ]}
            className="dms_add_domain_fqdn_field"
          >
            <Input.TextArea rows={2} placeholder={"example.com\nexample.net"} autoFocus />
          </Form.Item>

          <div className="grid grid-cols-2 gap-x-4">
            <Form.Item name="brand_id" label="Project" className="dms_add_domain_brand_field">
              <Select
                allowClear
                placeholder="Select a project"
                onChange={() => form.setFieldValue("project_brand_id", undefined)}
                options={brands.map((b) => ({ value: b.id, label: b.name }))}
                showSearch
                optionFilterProp="label"
                popupRender={(menu) => (
                  <>
                    {menu}
                    <Divider style={{ margin: "8px 0" }} />
                    <Button type="text" block icon={<PlusOutlined />} onClick={() => setIsBrandModalOpen(true)} className="dms_add_domain_create_brand_btn">
                      Create new project
                    </Button>
                  </>
                )}
              />
            </Form.Item>
            <Form.Item name="project_brand_id" label="Brand" className="dms_add_domain_project_brand_field">
              <Select
                allowClear
                showSearch
                optionFilterProp="label"
                disabled={!selectedProjectId}
                placeholder={selectedProjectId ? "Select a brand" : "Pick a project first"}
                options={(brands.find((b) => b.id === selectedProjectId)?.project_brands ?? []).map((b) => ({ value: b.id, label: b.name }))}
              />
            </Form.Item>
            <Form.Item name="region_id" label="Region" className="dms_add_domain_region_field">
              <Select
                allowClear
                placeholder="Select a region"
                options={regions.map((r) => ({ value: r.id, label: `${r.name} (${r.country_code})` }))}
              />
            </Form.Item>
            <Form.Item name="registrar_account_id" label="Registrar" className="dms_add_domain_registrar_field">
              <Select
                allowClear
                placeholder="Pick a registrar account"
                options={registrars.map((r) => ({ value: r.id, label: r.label }))}
              />
            </Form.Item>
            <Form.Item name="cf_account_id" label="Cloudflare account" className="dms_add_domain_cloudflare_field">
              <Select
                allowClear
                placeholder="Defaults to the project's account"
                options={cloudflareAccounts.map((a) => ({ value: a.id, label: a.label }))}
              />
            </Form.Item>
            <Form.Item name="server_account_id" label="Server" extra="An existing account, or a new site on a Huawei, AWS or BT Panel server." className="dms_add_domain_server_field">
              <Select
                allowClear
                showSearch
                optionFilterProp="label"
                placeholder="Pick a server"
                options={buildServerOptions(serverAccounts, serverGroups)}
              />
            </Form.Item>
            <Form.Item name="purpose" label="Purpose" className="dms_add_domain_purpose_field">
              <Select allowClear placeholder="What is this domain for?" options={PURPOSE_OPTIONS} />
            </Form.Item>
          </div>

          <Form.Item
            name="registrar_label"
            label="Or a label, if not on a managed registrar account"
            className="dms_add_domain_registrar_label_field"
          >
            <Input placeholder="e.g. Namecheap" />
          </Form.Item>

          <Form.Item name="notes" label="Notes" className="dms_add_domain_notes_field">
            <Input.TextArea rows={2} />
          </Form.Item>

          <Alert
            type="info"
            showIcon
            title="Proceed to setup creates the domain and its folder on the server, then sets Cloudflare up (DNS, HTTPS, bot protection, registrar nameservers). Save only just records the domain - nothing is touched."
            className="dms_add_domain_proceed_hint"
          />
        </Form>
      )}

      {step === 1 && (
        <div className="flex flex-col gap-3 dms_add_domain_setup">
          {tasks.map((task) => (
            <div key={task.key} className={`dms_add_domain_task dms_add_domain_task_${task.key}`}>
              <div className="flex items-center gap-2 font-medium">
                <StateIcon state={task.state} />
                {task.label}
              </div>
              {task.steps.length > 0 && (
                <ul className="text-xs ml-6 mt-1 opacity-80">
                  {task.steps.map((s, i) => (
                    <li key={i}>
                      {s.status === "failed" ? "x" : "-"} {s.message}
                    </li>
                  ))}
                </ul>
              )}
              {task.state === "failed" && task.error && (
                <Alert type="error" showIcon title={task.error} className="mt-2" />
              )}
            </div>
          ))}
          {allSetupDone && <Alert type="success" showIcon title="Setup finished. Next, choose what goes on the site." />}
        </div>
      )}

      {step === 2 && (
        <div className="flex flex-col gap-3 dms_add_domain_content">
          <Radio.Group
            value={contentChoice}
            onChange={(event) => setContentChoice(event.target.value)}
            optionType="button"
            buttonStyle="solid"
            disabled={!!contentDone || uploadPercent !== null || isInstalling}
            options={[
              { value: "zip", label: "Upload an HTML ZIP" },
              { value: "wordpress", label: "Set up WordPress" },
              { value: "later", label: "Do it later" },
            ]}
            className="dms_add_domain_content_choice"
          />

          {contentDone && <Alert type="success" showIcon title={contentDone} />}

          {contentChoice === "zip" && !contentDone && (
            <>
              <Upload.Dragger
                accept=".zip"
                multiple={false}
                showUploadList={false}
                disabled={uploadPercent !== null}
                beforeUpload={(file) => {
                  void handleZip(file);

                  return false;
                }}
                className="dms_add_domain_zip_dragger"
              >
                <p className="ant-upload-drag-icon">
                  <InboxOutlined />
                </p>
                <p className="ant-upload-text">Click or drag a ZIP file to deploy</p>
              </Upload.Dragger>
              {uploadPercent !== null && (
                <div role="status">
                  <div className="text-sm mb-1">
                    {uploadPercent < 100 ? `Uploading - ${uploadPercent}%` : "Upload complete - extracting the files on the server..."}
                  </div>
                  <Progress percent={uploadPercent} status="active" showInfo={uploadPercent < 100} />
                </div>
              )}
            </>
          )}

          {contentChoice === "wordpress" && !contentDone && (
            <Form<WordPressFormValues> form={wpForm} layout="vertical" onFinish={handleWordPress} className="dms_add_domain_wp_form">
              <Form.Item name="admin_user" label="Admin username" rules={[{ required: true, message: "Enter an admin username" }]}>
                <Input autoComplete="off" />
              </Form.Item>
              <Form.Item
                name="admin_password"
                label="Admin password"
                rules={[{ required: true, message: "Enter an admin password" }, { min: 8, message: "At least 8 characters" }]}
              >
                <Input.Password autoComplete="new-password" />
              </Form.Item>
              <Form.Item name="admin_email" label="Admin email" rules={[{ required: true, type: "email", message: "Enter a valid email" }]}>
                <Input autoComplete="off" />
              </Form.Item>
              <Button type="primary" htmlType="submit" loading={isInstalling} className="dms_add_domain_wp_install_btn">
                Install WordPress
              </Button>
              {isInstalling && <div className="text-sm mt-2">Installing WordPress - this can take a minute...</div>}
            </Form>
          )}

          {contentChoice === "later" && (
            <Alert type="info" showIcon title="You can upload a ZIP or set up WordPress any time from the domain's Deploy tab." />
          )}
        </div>
      )}

      <BrandFormModal open={isBrandModalOpen} onClose={() => setIsBrandModalOpen(false)} onSaved={handleBrandCreated} />
    </Modal>
  );
}
