"use client";

import type { AwsSecurityGroupRule, ServerGroup } from "@domainops/shared-types";
import { Alert, App, Button, Checkbox, Form, Input, InputNumber, Modal, Popconfirm, Select, Table, Tag, Typography } from "antd";
import { DeleteOutlined } from "@ant-design/icons";
import type { ColumnsType } from "antd/es/table";
import { useCallback, useEffect, useState } from "react";
import {
  createServerGroupSecurityRule,
  deleteServerGroupSecurityRule,
  listServerGroupSecurityRules,
  openServerPorts,
} from "@/lib/api/servers";
import { CopyIpButton, useAppIp } from "./AppIpHint";
import { ApiError } from "@/lib/api/client";

interface RuleFormValues {
  cidr: string;
  port: number;
  protocol: "tcp" | "udp" | "icmp";
  description?: string;
}

/**
 * EC2 Security Group ingress rules - allow-only, unlike Cloudflare's
 * security rules (ZoneSecurityModal), which support both block and allow.
 * There's no "block an IP" concept on a Security Group, so this form is
 * intentionally just an allow-list with add/remove, not a mirror of the
 * Cloudflare block/allow UI.
 */
const WIDE_OPEN_CIDRS = ["0.0.0.0/0", "::/0"];

function panelPort(group: ServerGroup): number | null {
  if (!group.url || !["btpanel", "huawei_bt"].includes(group.connector_type)) return null;

  try {
    const url = new URL(group.url);

    return Number(url.port) || (url.protocol === "http:" ? 80 : 443);
  } catch {
    return null;
  }
}

function sensitivePorts(group: ServerGroup): number[] {
  return [22, group.ssh_port, panelPort(group), 3306].filter((port): port is number => typeof port === "number");
}

export function ServerSecurityGroupModal({
  open,
  group,
  onClose,
}: {
  open: boolean;
  group: ServerGroup | null;
  onClose: () => void;
}) {
  const { message } = App.useApp();
  const [rules, setRules] = useState<AwsSecurityGroupRule[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [form] = Form.useForm<RuleFormValues>();
  const appIp = useAppIp(open);
  const [isOpeningPorts, setIsOpeningPorts] = useState(false);
  const [chosenPorts, setChosenPorts] = useState<number[] | null>(null);

  const portOptions = group
    ? [
        ...(["cpanel", "aws", "huawei"].includes(group.connector_type)
          ? [{ value: group.ssh_port || 22, label: `SSH (${group.ssh_port || 22})` }]
          : []),
        ...(panelPort(group) ? [{ value: panelPort(group) as number, label: `BT Panel (${panelPort(group)})` }] : []),
      ]
    : [];
  const portsToOpen = chosenPorts ?? portOptions.map((option) => option.value);

  const loadRules = useCallback(async () => {
    if (!group) return;
    setIsLoading(true);
    try {
      setRules(await listServerGroupSecurityRules(group.id));
    } catch (err) {
      message.error(err instanceof ApiError ? err.message : "Couldn't load security group rules.");
    } finally {
      setIsLoading(false);
    }
  }, [group, message]);

  useEffect(() => {
    if (!open) return;
    // eslint-disable-next-line react-hooks/set-state-in-effect
    void loadRules();
  }, [open, loadRules]);

  async function handleAddRule(values: RuleFormValues) {
    if (!group) return;
    try {
      await createServerGroupSecurityRule(group.id, values);
      message.success(`Allowed ${values.cidr} on port ${values.port}.`);
      form.resetFields();
      void loadRules();
    } catch (err) {
      message.error(err instanceof ApiError ? err.message : "Couldn't add that rule.");
    }
  }

  async function handleOpenPorts() {
    if (!group) return;
    setIsOpeningPorts(true);
    try {
      const result = await openServerPorts(group.id, { ports: portsToOpen });
      const failed = result.results.filter((item) => !item.success);

      if (failed.length > 0) {
        message.error(failed.map((item) => `Port ${item.port}: ${item.message}`).join(" "), 8);
      } else {
        message.success(`Opened port${result.results.length > 1 ? "s" : ""} ${result.results.map((item) => item.port).join(", ")} for ${result.cidr}.`);
      }

      void loadRules();
    } catch (err) {
      message.error(err instanceof ApiError ? err.message : "Couldn't open those ports.");
    } finally {
      setIsOpeningPorts(false);
    }
  }

  async function handleRestrict(rule: AwsSecurityGroupRule) {
    if (!group || !appIp || rule.port === null) return;
    try {
      await createServerGroupSecurityRule(group.id, { cidr: `${appIp}/32`, port: rule.port, protocol: rule.protocol as "tcp" | "udp" | "icmp", description: "Domain/Ops" });
      await deleteServerGroupSecurityRule(group.id, rule.id);
      message.success(`Port ${rule.port} now allows only ${appIp}.`);
      void loadRules();
    } catch (err) {
      message.error(err instanceof ApiError ? err.message : "Couldn't restrict that rule.");
    }
  }

  async function handleDeleteRule(rule: AwsSecurityGroupRule) {
    if (!group) return;
    try {
      await deleteServerGroupSecurityRule(group.id, rule.id);
      message.success("Rule removed.");
      void loadRules();
    } catch (err) {
      message.error(err instanceof ApiError ? err.message : "Couldn't remove that rule.");
    }
  }

  const columns: ColumnsType<AwsSecurityGroupRule> = [
    {
      title: "CIDR",
      dataIndex: "cidr",
      render: (v: string, rule) => {
        const isRisky = WIDE_OPEN_CIDRS.includes(v) && rule.port !== null && group !== null && sensitivePorts(group).includes(rule.port);

        return (
          <span className="flex items-center gap-2 flex-wrap">
            <span className="font-mono">{v}</span>
            {isRisky && (
              <>
                <Tag color="red" className="!m-0">
                  Open to everyone
                </Tag>
                {appIp && (
                  <Popconfirm title={`Allow only ${appIp} on port ${rule.port}?`} description="Adds the app's address, then removes this wide-open rule." okText="Restrict" onConfirm={() => handleRestrict(rule)}>
                    <Button size="small" className="dms_servers_security_group_restrict_btn">
                      Restrict to this app
                    </Button>
                  </Popconfirm>
                )}
              </>
            )}
          </span>
        );
      },
    },
    { title: "Port", dataIndex: "port", render: (v: number | null) => v ?? "any" },
    { title: "Protocol", dataIndex: "protocol", render: (v: string) => v.toUpperCase() },
    { title: "Description", dataIndex: "description", render: (v: string | null) => v ?? "—" },
    {
      title: "",
      render: (_, rule) => (
        <Button
          size="small"
          danger
          icon={<DeleteOutlined />}
          onClick={() => handleDeleteRule(rule)}
          className="dms_servers_security_group_remove_rule_btn"
        />
      ),
    },
  ];

  return (
    <Modal
      title={group ? `Security Group - ${group.label}` : "Security Group"}
      open={open}
      onCancel={onClose}
      footer={null}
      width={680}
      destroyOnHidden
      className="dms_servers_security_group_modal"
    >
      {group && !group.aws_security_group_id && (
        <Typography.Text type="secondary">
          Set a Security Group ID on this server group first (Edit &gt; AWS fields).
        </Typography.Text>
      )}
      {portOptions.length > 0 && (
        <Alert
          type="info"
          showIcon
          className="mb-3 dms_servers_open_ports_panel"
          title={
            <span className="flex items-center gap-1 flex-wrap">
              Let this app in {appIp ? <span className="font-mono">({appIp})</span> : "(its public IP could not be found)"}
              {appIp && <CopyIpButton ip={appIp} />}
            </span>
          }
          description={
            <div className="flex flex-col gap-2">
              <span>Adds an allow rule for this app&apos;s address on the ports below, in one click. Existing rules are kept.</span>
              <div className="flex items-center gap-3 flex-wrap">
                <Checkbox.Group options={portOptions} value={portsToOpen} onChange={(values) => setChosenPorts(values as number[])} />
                <Button
                  type="primary"
                  size="small"
                  loading={isOpeningPorts}
                  disabled={!appIp || portsToOpen.length === 0}
                  onClick={() => void handleOpenPorts()}
                  className="dms_servers_open_ports_btn"
                >
                  Open for this app
                </Button>
              </div>
            </div>
          }
        />
      )}
      <Form<RuleFormValues>
        form={form}
        layout="inline"
        onFinish={handleAddRule}
        initialValues={{ protocol: "tcp" }}
        className="mb-3 flex-wrap gap-2 dms_servers_security_group_form"
      >
        <Form.Item name="cidr" rules={[{ required: true, message: "CIDR" }]} className="dms_servers_security_group_cidr_field">
          <Input placeholder="203.0.113.10/32" style={{ width: 180 }} />
        </Form.Item>
        <Form.Item name="port" rules={[{ required: true, message: "Port" }]} className="dms_servers_security_group_port_field">
          <InputNumber min={1} max={65535} placeholder="443" style={{ width: 100 }} />
        </Form.Item>
        <Form.Item name="protocol" className="dms_servers_security_group_protocol_field">
          <Select
            style={{ width: 100 }}
            options={[
              { value: "tcp", label: "TCP" },
              { value: "udp", label: "UDP" },
              { value: "icmp", label: "ICMP" },
            ]}
          />
        </Form.Item>
        <Form.Item name="description" className="dms_servers_security_group_description_field">
          <Input placeholder="Office IP" style={{ width: 160 }} />
        </Form.Item>
        <Form.Item className="dms_servers_security_group_submit_field">
          <Button type="primary" htmlType="submit" className="dms_servers_security_group_add_btn">
            Allow
          </Button>
        </Form.Item>
      </Form>
      <Table<AwsSecurityGroupRule>
        size="small"
        rowKey="id"
        loading={isLoading}
        dataSource={rules}
        columns={columns}
        pagination={false}
        className="dms_servers_security_group_rules_table"
        locale={{ emptyText: "No ingress rules allowed yet." }}
      />
    </Modal>
  );
}
