import { describe, it, expect } from 'vitest';
import type { Program, SourceLocation } from '@promptscript/core';
import { requiredMetaId, requiredMetaSyntax } from '../rules/required-meta.js';
import { validSemver, isValidSemver } from '../rules/valid-semver.js';
import type { RuleContext, ValidationMessage, ValidatorConfig } from '../types.js';

/**
 * Create a minimal test AST.
 */
function createTestProgram(overrides: Partial<Program> = {}): Program {
  const defaultLoc: SourceLocation = { file: 'test.prs', line: 1, column: 1 };
  return {
    type: 'Program',
    loc: defaultLoc,
    meta: {
      type: 'MetaBlock',
      loc: defaultLoc,
      fields: {
        id: 'test-project',
        syntax: '1.0.0',
      },
    },
    uses: [],
    blocks: [],
    extends: [],
    ...overrides,
  };
}

/**
 * Create a rule context for testing.
 */
function createRuleContext(
  ast: Program,
  config: ValidatorConfig = {}
): { ctx: RuleContext; messages: ValidationMessage[] } {
  const messages: ValidationMessage[] = [];
  const ctx: RuleContext = {
    ast,
    config,
    report: (msg) => {
      messages.push({
        ruleId: 'TEST',
        ruleName: 'test',
        severity: 'error',
        ...msg,
      });
    },
  };
  return { ctx, messages };
}

describe('required-meta rules', () => {
  describe('requiredMetaId (PS001)', () => {
    it('should have correct metadata', () => {
      expect(requiredMetaId.id).toBe('PS001');
      expect(requiredMetaId.name).toBe('required-meta-id');
      expect(requiredMetaId.defaultSeverity).toBe('error');
    });

    it('should pass when id is present', () => {
      const ast = createTestProgram();
      const { ctx, messages } = createRuleContext(ast);
      requiredMetaId.validate(ctx);
      expect(messages).toHaveLength(0);
    });

    it('should report when @meta block is missing', () => {
      const ast = createTestProgram({ meta: undefined });
      const { ctx, messages } = createRuleContext(ast);
      requiredMetaId.validate(ctx);
      expect(messages).toHaveLength(1);
      expect(messages[0]!.message).toContain('@meta block is required');
    });

    it('should report when id field is missing', () => {
      const ast = createTestProgram();
      ast.meta!.fields = { syntax: '1.0.0' };
      const { ctx, messages } = createRuleContext(ast);
      requiredMetaId.validate(ctx);
      expect(messages).toHaveLength(1);
      expect(messages[0]!.message).toContain('"id" field');
    });
  });

  describe('requiredMetaSyntax (PS002)', () => {
    it('should have correct metadata', () => {
      expect(requiredMetaSyntax.id).toBe('PS002');
      expect(requiredMetaSyntax.name).toBe('required-meta-syntax');
      expect(requiredMetaSyntax.defaultSeverity).toBe('error');
    });

    it('should pass when syntax is present', () => {
      const ast = createTestProgram();
      const { ctx, messages } = createRuleContext(ast);
      requiredMetaSyntax.validate(ctx);
      expect(messages).toHaveLength(0);
    });

    it('should not report when @meta block is missing (PS001 handles that)', () => {
      const ast = createTestProgram({ meta: undefined });
      const { ctx, messages } = createRuleContext(ast);
      requiredMetaSyntax.validate(ctx);
      expect(messages).toHaveLength(0);
    });

    it('should report when syntax field is missing', () => {
      const ast = createTestProgram();
      ast.meta!.fields = { id: 'test' };
      const { ctx, messages } = createRuleContext(ast);
      requiredMetaSyntax.validate(ctx);
      expect(messages).toHaveLength(1);
      expect(messages[0]!.message).toContain('"syntax" field');
    });
  });
});

describe('valid-semver rule (PS003)', () => {
  describe('isValidSemver', () => {
    it('should accept valid semver versions', () => {
      expect(isValidSemver('1.0.0')).toBe(true);
      expect(isValidSemver('0.0.1')).toBe(true);
      expect(isValidSemver('10.20.30')).toBe(true);
      expect(isValidSemver('1.0.0-alpha')).toBe(true);
      expect(isValidSemver('1.0.0-alpha.1')).toBe(true);
      expect(isValidSemver('1.0.0+build')).toBe(true);
      expect(isValidSemver('1.0.0-alpha+build')).toBe(true);
    });

    it('should reject invalid versions', () => {
      expect(isValidSemver('1.0')).toBe(false);
      expect(isValidSemver('1')).toBe(false);
      expect(isValidSemver('v1.0.0')).toBe(false);
      expect(isValidSemver('1.0.0.0')).toBe(false);
      expect(isValidSemver('01.0.0')).toBe(false);
      expect(isValidSemver('1.00.0')).toBe(false);
      expect(isValidSemver('invalid')).toBe(false);
      expect(isValidSemver('')).toBe(false);
    });

    it('should reject version starting with prerelease separator', () => {
      // Tests the case where mainVersion is empty after split
      expect(isValidSemver('-alpha')).toBe(false);
      expect(isValidSemver('+build')).toBe(false);
      expect(isValidSemver('-1.0.0')).toBe(false);
      expect(isValidSemver('+1.0.0')).toBe(false);
    });
  });

  describe('validSemver rule', () => {
    it('should have correct metadata', () => {
      expect(validSemver.id).toBe('PS003');
      expect(validSemver.name).toBe('valid-semver');
      expect(validSemver.defaultSeverity).toBe('error');
    });

    it('should pass for valid semver', () => {
      const ast = createTestProgram();
      const { ctx, messages } = createRuleContext(ast);
      validSemver.validate(ctx);
      expect(messages).toHaveLength(0);
    });

    it('should report invalid semver', () => {
      const ast = createTestProgram();
      ast.meta!.fields = { id: 'test', syntax: 'not-a-version' };
      const { ctx, messages } = createRuleContext(ast);
      validSemver.validate(ctx);
      expect(messages).toHaveLength(1);
      expect(messages[0]!.message).toContain('Invalid semantic version');
    });

    it('should report non-string syntax', () => {
      const ast = createTestProgram();
      ast.meta!.fields = { id: 'test', syntax: 123 };
      const { ctx, messages } = createRuleContext(ast);
      validSemver.validate(ctx);
      expect(messages).toHaveLength(1);
      expect(messages[0]!.message).toContain('must be a string');
    });

    it('should not report when syntax is missing (PS002 handles that)', () => {
      const ast = createTestProgram();
      ast.meta!.fields = { id: 'test' };
      const { ctx, messages } = createRuleContext(ast);
      validSemver.validate(ctx);
      expect(messages).toHaveLength(0);
    });
  });
});
