/* @vitest-environment happy-dom */

/* eslint-disable vue/one-component-per-file */

import type { SystemNetworkApi } from '#/api/system/network';

import { flushPromises, mount } from '@vue/test-utils';
import { defineComponent, h } from 'vue';

import NetworkDdnsTable, {
  getDdnsRetryDisabledReason,
} from '@test-source/apps/web-antdv-next/src/views/system/network/components/NetworkDdnsTable';
import { beforeEach, describe, expect, it, vi } from 'vitest';

const mocks = vi.hoisted(() => ({
  api: {
    deleteRecord: vi.fn(),
    getList: vi.fn(),
    getProviderStatus: vi.fn(),
    retryRecord: vi.fn(),
  },
  messageSuccess: vi.fn(),
  modalOpenCreate: vi.fn(),
  modalOpenEdit: vi.fn(),
  tableApi: {
    reload: vi.fn(),
  },
  tableOptions: undefined as any,
}));

vi.mock('antdv-next', () => ({
  message: { success: mocks.messageSuccess },
  Space: defineComponent({
    name: 'MockSpace',
    setup(_, { slots }) {
      return () => h('span', slots.default?.());
    },
  }),
  Tag: defineComponent({
    name: 'MockTag',
    setup(_, { slots }) {
      return () => h('span', slots.default?.());
    },
  }),
  Typography: {
    Text: defineComponent({
      name: 'MockText',
      setup(_, { slots }) {
        return () => h('span', slots.default?.());
      },
    }),
  },
}));

vi.mock('#/components/ktTable', () => ({
  KtTable: defineComponent({
    name: 'MockKtTable',
    setup(_, { slots }) {
      return () =>
        h('section', { 'data-testid': 'ddns-table' }, [
          slots.headerControls?.(),
          slots.bodyCell?.({
            column: { key: 'identity' },
            record: createDdnsRow(),
          }),
          slots.bodyCell?.({
            column: { key: 'syncStatus' },
            record: createDdnsRow(),
          }),
        ]);
    },
  }),
  useKtTable: vi.fn((options) => {
    mocks.tableOptions = options;
    return [vi.fn(), mocks.tableApi];
  }),
}));

vi.mock(
  '@test-source/apps/web-antdv-next/src/views/system/network/components/NetworkDdnsRecordModal',
  () => ({
    default: defineComponent({
      name: 'MockNetworkDdnsRecordModal',
      setup(_, { expose }) {
        expose({
          openCreate: mocks.modalOpenCreate,
          openEdit: mocks.modalOpenEdit,
        });
        return () => h('div');
      },
    }),
  }),
);

vi.mock('#/api/system/network', () => ({
  deleteNetworkDdnsRecord: mocks.api.deleteRecord,
  getNetworkDdnsList: mocks.api.getList,
  getNetworkDdnsProviderStatus: mocks.api.getProviderStatus,
  retryNetworkDdnsRecord: mocks.api.retryRecord,
}));

vi.mock('#/locales', () => ({
  $t: (key: string) => key,
}));

function createDdnsRow(
  overrides: Partial<SystemNetworkApi.DdnsRecord> = {},
): SystemNetworkApi.DdnsRecord {
  return {
    appliedAddress: '123.45.67.89',
    domain: 'kwitsukasa.top',
    enabled: true,
    fqdn: 'nas.kwitsukasa.top',
    id: '90071992547409930',
    lastSyncedAt: '2026-07-23T08:00:00.000Z',
    name: 'NAS IPv4',
    portForwardId: '90071992547409931',
    recordType: 'A',
    retryCount: 0,
    source: {
      currentAddress: '123.45.67.89',
      eligible: true,
      externalPort: 45_678,
      id: '90071992547409931',
      name: 'NAS UDP',
      protocol: 'udp',
      sourceType: 'port_forward_ipv4',
    },
    sourceAddress: '123.45.67.89',
    sourceType: 'port_forward_ipv4',
    subDomain: 'nas',
    syncStatus: 'synced',
    updateTime: '2026-07-23T08:00:00.000Z',
    ...overrides,
  };
}

describe('network DDNS table', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mocks.api.getProviderStatus.mockResolvedValue({
      configured: true,
      enabled: true,
      provider: 'dnspod',
    });
    mocks.api.deleteRecord.mockResolvedValue({});
    mocks.api.retryRecord.mockResolvedValue({});
    mocks.tableApi.reload.mockResolvedValue(undefined);
  });

  it('uses an independent KtTable with dual-stack address columns', () => {
    const wrapper = mount(NetworkDdnsTable);

    expect(wrapper.find('[data-testid="ddns-table"]').exists()).toBe(true);
    expect(mocks.tableOptions.immediate).toBe(false);
    expect(mocks.tableOptions.columns.map((item: any) => item.key)).toEqual([
      'identity',
      'source',
      'sourceAddress',
      'appliedAddress',
      'syncStatus',
      'lastSync',
      'updateTime',
    ]);
    expect(mocks.tableOptions.tableTitle).toBe('system.network.ddnsTitle');
  });

  it('shows API-provided FQDN without appending the source external port', () => {
    const wrapper = mount(NetworkDdnsTable);

    expect(wrapper.text()).toContain('nas.kwitsukasa.top');
    expect(wrapper.text()).not.toContain('45678');
  });

  it('keeps retry visible but disables it for every unsafe state with an exact reason', () => {
    mount(NetworkDdnsTable);
    const retry = mocks.tableOptions.rowActions.find(
      (item: any) => item.key === 'retry',
    );

    expect(retry.rowVisible).toBeUndefined();
    expect(retry.disabled(createDdnsRow({ enabled: false }))).toBe(true);
    expect(getDdnsRetryDisabledReason(createDdnsRow({ enabled: false }))).toBe(
      'system.network.ddnsRetryDisabled',
    );
    expect(
      getDdnsRetryDisabledReason(createDdnsRow({ syncStatus: 'syncing' })),
    ).toBe('system.network.ddnsSyncInProgress');
    expect(
      getDdnsRetryDisabledReason(
        createDdnsRow({
          source: {
            ...createDdnsRow().source,
            disabledReasonCode: 'KEEPER_DISABLED',
            eligible: false,
          },
        }),
      ),
    ).toContain('KEEPER_DISABLED');
    expect(
      getDdnsRetryDisabledReason(
        createDdnsRow({
          source: { ...createDdnsRow().source, currentAddress: null },
          sourceAddress: null,
        }),
      ),
    ).toBe('system.network.ddnsSourceAddressMissing');
  });

  it('uses scoped permissions and explains that delete preserves DNSPod records', () => {
    mount(NetworkDdnsTable);

    expect(mocks.tableOptions.buttons[0].permissionCodes).toEqual([
      'System:Network:Ddns:Create',
    ]);
    expect(
      mocks.tableOptions.rowActions.map((item: any) => [
        item.key,
        item.permissionCodes,
      ]),
    ).toEqual([
      ['edit', ['System:Network:Ddns:Update']],
      ['retry', ['System:Network:Ddns:Retry']],
      ['delete', ['System:Network:Ddns:Delete']],
    ]);
    const deleteAction = mocks.tableOptions.rowActions.find(
      (item: any) => item.key === 'delete',
    );
    expect(deleteAction.confirm(createDdnsRow())).toBe(
      'system.network.ddnsDeleteConfirm',
    );
  });

  it('opens create/edit modals and refreshes both list and provider status on demand', async () => {
    const wrapper = mount(NetworkDdnsTable);
    const row = createDdnsRow();

    await mocks.tableOptions.buttons[0].onClick({});
    await mocks.tableOptions.rowActions
      .find((item: any) => item.key === 'edit')
      .onClick(row, {});
    await (wrapper.vm as any).reload();
    await flushPromises();

    expect(mocks.modalOpenCreate).toHaveBeenCalledOnce();
    expect(mocks.modalOpenEdit).toHaveBeenCalledWith(row);
    expect(mocks.tableApi.reload).toHaveBeenCalledOnce();
    expect(mocks.api.getProviderStatus).toHaveBeenCalledOnce();
  });

  it('supports IPv6 source and applied addresses without IPv4-specific labels', () => {
    mount(NetworkDdnsTable);
    const row = createDdnsRow({
      appliedAddress: '2409:8a31::1',
      fqdn: 'nas6.kwitsukasa.top',
      portForwardId: null,
      recordType: 'AAAA',
      source: {
        currentAddress: '2409:8a31::1',
        eligible: true,
        id: 'agent-ipv6',
        name: 'Agent IPv6',
        sourceType: 'agent_ipv6',
      },
      sourceAddress: '2409:8a31::1',
      sourceType: 'agent_ipv6',
      subDomain: 'nas6',
    });
    const renderBodyCell = (mocks.tableOptions as any).renderBodyCell;

    expect(row.source.currentAddress).toBe('2409:8a31::1');
    expect(JSON.stringify(mocks.tableOptions.columns)).not.toMatch(
      /sourceIpv4|appliedIpv4/i,
    );
    expect(renderBodyCell).toBeUndefined();
  });
});
