/**
 * Phase 2 — mongodb-native controller-generator emits guards import +
 * extended validate() body when the model has declared constraints
 * (mirrors the prisma controller-with-constraints test).
 *
 * Smoke-shape test: asserts STRINGS in the generated controller source.
 */

import { describe, it, expect } from 'vitest';
import generateMongoNativeController from '../controller-generator.js';
import type { ModelConstraintSpec } from '@specverse/types';

function buildConstrainedModel(): any {
  const expanded: ModelConstraintSpec = {
    on: ['create'],
    requires: {
      type: 'guard',
      name: 'Vote_create_requires',
      body: 'self.poll.votingStatus == "open"',
      params: 'self: Vote, actor: User',
      source: {
        convention: 'compound',
        entity: 'constraints',
        input: 'self.poll.votingStatus == "open"',
      },
    },
    source: {
      authorOn: 'create',
      authorRequires: 'self.poll.votingStatus == "open"',
    },
  };
  return {
    name: 'Vote',
    attributes: [
      { name: 'id', type: 'UUID', required: true, unique: true, category: 'metadata', auto: 'uuid4' },
      { name: 'choice', type: 'String', required: true, unique: false, category: 'business' },
    ],
    relationships: [],
    lifecycles: [],
    behaviors: {},
    constraints: [expanded],
  };
}

function buildUnconstrainedModel(): any {
  return {
    name: 'Comment',
    attributes: [
      { name: 'id', type: 'UUID', required: true, unique: true, category: 'metadata', auto: 'uuid4' },
      { name: 'text', type: 'String', required: true, unique: false, category: 'business' },
    ],
    relationships: [],
    lifecycles: [],
    behaviors: {},
  };
}

function buildController(modelName: string): any {
  return {
    name: `${modelName}Controller`,
    model: modelName,
    modelReference: modelName,
    cured: { create: {}, retrieve: {}, update: {}, validate: {}, delete: {} },
  };
}

describe('Phase 2 — controller-generator with model.constraints', () => {
  it('emits import for guards module when model has constraints', () => {
    const code = generateMongoNativeController({
      spec: {} as any,
      factory: {} as any,
      model: buildConstrainedModel(),
      controller: buildController('Vote'),
      models: [buildConstrainedModel()],
    });
    expect(code).toContain(`import { runGuards as runConstraintGuards } from './Vote.guards.js';`);
  });

  it('does NOT emit guards import for unconstrained models', () => {
    const code = generateMongoNativeController({
      spec: {} as any,
      factory: {} as any,
      model: buildUnconstrainedModel(),
      controller: buildController('Comment'),
      models: [buildUnconstrainedModel()],
    });
    expect(code).not.toContain('.guards.js');
    expect(code).not.toContain('runConstraintGuards');
  });

  it('extends validate() signature with actor + widens op union', () => {
    const code = generateMongoNativeController({
      spec: {} as any,
      factory: {} as any,
      model: buildConstrainedModel(),
      controller: buildController('Vote'),
      models: [buildConstrainedModel()],
    });
    expect(code).toContain('_actor: any = null');
    // Wider union includes 'delete' and the template-literal evolve.<X>.
    expect(code).toContain(`'delete'`);
    expect(code).toContain('`evolve.${string}`');
  });

  it('emits runConstraintGuards call inside validate() body', () => {
    const code = generateMongoNativeController({
      spec: {} as any,
      factory: {} as any,
      model: buildConstrainedModel(),
      controller: buildController('Vote'),
      models: [buildConstrainedModel()],
    });
    expect(code).toContain('await runConstraintGuards(_data, _context.operation, _actor, __guardCtx)');
  });

  // Slice 14 actor wiring — validate() ALWAYS accepts _actor (default null)
  // on EVERY controller for uniform route-handler call sites.
  it('emits _actor param on validate() signature for both constrained AND unconstrained', () => {
    const constrained = generateMongoNativeController({
      spec: {} as any,
      factory: {} as any,
      model: buildConstrainedModel(),
      controller: buildController('Vote'),
      models: [buildConstrainedModel()],
    });
    const unconstrained = generateMongoNativeController({
      spec: {} as any,
      factory: {} as any,
      model: buildUnconstrainedModel(),
      controller: buildController('Comment'),
      models: [buildUnconstrainedModel()],
    });
    expect(constrained).toContain('_actor: any = null');
    expect(unconstrained).toContain('_actor: any = null');
    expect(unconstrained).not.toContain('runConstraintGuards');
  });

  it('emits delete() with validate call when constrained', () => {
    const code = generateMongoNativeController({
      spec: {} as any,
      factory: {} as any,
      model: buildConstrainedModel(),
      controller: buildController('Vote'),
      models: [buildConstrainedModel()],
    });
    expect(code).toContain('public async delete(id: string, _actor: any = null)');
    expect(code).toContain(`await this.validate(vote, { operation: 'delete' }, _actor)`);
  });

  it('emits delete() WITHOUT validate call when unconstrained (backward compat)', () => {
    const code = generateMongoNativeController({
      spec: {} as any,
      factory: {} as any,
      model: buildUnconstrainedModel(),
      controller: buildController('Comment'),
      models: [buildUnconstrainedModel()],
    });
    expect(code).toContain('public async delete(id: string, _actor: any = null)');
    const deleteSection = code.substring(code.indexOf('public async delete(id: string'));
    expect(deleteSection).not.toContain('this.validate(');
  });

  // Phase 2 carry-over (Update self-from-DB).
  it('update() loads + merges entity before validate when constrained', () => {
    const code = generateMongoNativeController({
      spec: {} as any,
      factory: {} as any,
      model: buildConstrainedModel(),
      controller: buildController('Vote'),
      models: [buildConstrainedModel()],
    });
    const updateSection = code.substring(code.indexOf('public async update(id: string'));
    expect(updateSection).toContain('__existing');
    expect(updateSection).toContain('findOne');
    expect(updateSection).toContain('__merged');
    expect(updateSection).toContain(`await this.validate(__merged, { operation: 'update' }, _actor)`);
  });

  it('update() uses raw input shape when unconstrained (backward compat)', () => {
    const code = generateMongoNativeController({
      spec: {} as any,
      factory: {} as any,
      model: buildUnconstrainedModel(),
      controller: buildController('Comment'),
      models: [buildUnconstrainedModel()],
    });
    const updateSection = code.substring(code.indexOf('public async update(id: string'));
    expect(updateSection).not.toContain('__existing');
    expect(updateSection).not.toContain('__merged');
    expect(updateSection).toContain(`await this.validate(data, { operation: 'update' }, _actor)`);
  });
});
