import React from 'react';
import { render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';

import { parentControlledSharingEdge, controlledByParentAuthoring } from '../src/platform/infrastructure/api/users';
import { ObjectPermissionsTable } from '../src/platform/components/permissions/ObjectPermissionsTable';
import type { SObjectDescribe } from '../src/platform/types';

/**
 * app-packs#607 — the "Controlled by <parent>" label never rendered.
 *
 * `parentControlledSharingEdge` gated on `sharingParentField` and on finding
 * that field in `schema.fields`. Neither is present on a describe payload — the
 * global describe entry carries `sharingParentObject` and nothing else of the
 * edge — so the gate could only ever fail, and every parent-controlled object
 * fell through to a message claiming its sharing relationship was unconfigured.
 *
 * The fixtures below are deliberately shaped like the real `/sobjects` wire
 * response. Spelling `sharingParentField` into them would recreate the original
 * defect: a test passing against a payload the server never sends.
 */

/** Exactly what describeGlobalObject emits for a parent-controlled child. */
function describeGlobalEntry(overrides: Partial<SObjectDescribe> = {}): SObjectDescribe {
  return {
    name: 'Contact',
    label: 'Contact',
    labelPlural: 'Contacts',
    custom: false,
    keyPrefix: '003',
    createable: true,
    updateable: true,
    deletable: true,
    retrieveable: true,
    searchable: true,
    triggerable: true,
    undeletable: true,
    mergeable: false,
    deprecatedAndHidden: false,
    layoutable: true,
    queryable: true,
    replicateable: true,
    feedEnabled: true,
    sharingModel: 'ControlledByParent',
    sharingParentObject: 'Account',
    tableType: 'standard_object',
    ...overrides,
  } as SObjectDescribe;
}

describe('parentControlledSharingEdge', () => {
  it('resolves the parent from a describe payload, which carries no field-level edge', () => {
    const schema = describeGlobalEntry();
    // Guard the premise: if a future fixture starts spelling these, the test
    // stops covering the shape that actually broke.
    expect('sharingParentField' in schema).toBe(false);
    expect(schema.fields).toBeUndefined();

    expect(parentControlledSharingEdge(schema)).toEqual({ parentObject: 'Account' });
  });

  it('returns null when the backend resolved no sharing parent', () => {
    // Empty means describe found no IsSharingParent relationship for this child.
    expect(parentControlledSharingEdge(describeGlobalEntry({ sharingParentObject: '' }))).toBeNull();
    expect(parentControlledSharingEdge(describeGlobalEntry({ sharingParentObject: '   ' }))).toBeNull();
    expect(parentControlledSharingEdge(describeGlobalEntry({ sharingParentObject: undefined }))).toBeNull();
  });

  it('returns null for objects that are not parent-controlled', () => {
    expect(parentControlledSharingEdge(describeGlobalEntry({ sharingModel: 'Private' }))).toBeNull();
  });

  it('still reports an unresolved child as non-authorable', () => {
    // The authorability predicate keys off sharingModel alone, matching the
    // backend's RejectParentControlledChildWrite. Losing the parent name must
    // never make a child look editable.
    const unresolved = describeGlobalEntry({ sharingParentObject: '' });
    expect(controlledByParentAuthoring(unresolved)).toEqual({ parentObject: null });
  });
});

describe('ObjectPermissionsTable — parent-controlled rows', () => {
  // Rows come from the ObjectPermissions list; `schemas` only supplies the label
  // and the controlled-by-parent status, so a row must exist for the object.
  function renderTable(schemas: SObjectDescribe[]) {
    const permissions = schemas.map(s => ({
      Id: `op-${s.name}`,
      SobjectType: s.name,
      PermissionsRead: false,
      PermissionsCreate: false,
      PermissionsEdit: false,
      PermissionsDelete: false,
      PermissionsViewAllRecords: false,
      PermissionsModifyAllRecords: false,
    })) as unknown as React.ComponentProps<typeof ObjectPermissionsTable>['permissions'];

    return render(
      <ObjectPermissionsTable permissions={permissions} schemas={schemas} onToggle={() => {}} />,
    );
  }

  it('names the parent instead of claiming the relationship is unconfigured', () => {
    renderTable([describeGlobalEntry()]);

    expect(screen.getByText('Account')).toBeInTheDocument();
    expect(screen.getByText(/Controlled by/)).toBeInTheDocument();
    expect(screen.queryByText(/not configured/i)).not.toBeInTheDocument();
  });

  it('does not diagnose the metadata when the parent is unresolved', () => {
    // Parent-controlled without a direct sharing-parent relationship is a real
    // shape (two-step). Accusing it of being misconfigured sends admins looking
    // for a problem that may not exist.
    renderTable([describeGlobalEntry({ sharingParentObject: '' })]);

    expect(screen.getByText(/Controlled by its parent record/)).toBeInTheDocument();
    expect(screen.queryByText(/not configured/i)).not.toBeInTheDocument();
  });

  it('renders the label as body text, not as de-emphasised placeholder styling', () => {
    // It states which object owns these permissions, so it has to read as
    // content. Italic + slate-400/500 made it look like a disabled hint.
    const { container } = renderTable([describeGlobalEntry()]);

    const cell = container.querySelector('td[colspan="6"]');
    expect(cell).not.toBeNull();
    expect(cell?.className).not.toMatch(/italic/);
    expect(cell?.className).toMatch(/text-slate-700/);
    expect(screen.getByText('Account').className).toMatch(/font-semibold/);
  });
});
