/**
 * Backend Integration Test Generator
 * Generates API integration tests using Jest + Supertest
 */

import { TestGenerator, GeneratorContext, GenerationResult, GeneratedFile } from '../types';
import * as path from 'path';

export class BackendTestGenerator implements TestGenerator {
  id = 'backend-integration-tests';
  name = 'Backend Integration Test Generator';
  description = 'Generates API integration tests with Jest and Supertest';
  testFramework = 'jest';

  generate(context: GeneratorContext): GenerationResult {
    const { workflow, outputDirectory } = context;
    const files: GeneratedFile[] = [];

    if (!workflow.testing?.unit_tests && !workflow.testing?.integration_tests) {
      return { files: [] };
    }

    // Generate unit tests for backend endpoints
    if (workflow.testing.unit_tests) {
      const backendTests = workflow.testing.unit_tests.filter(test => {
        const [componentName] = test.component.split('.');
        const component = workflow.stack[componentName];
        return component?.type === 'backend';
      });

      for (const test of backendTests) {
        const [componentName, endpointId] = test.component.split('.');
        const stackComponent = workflow.stack[componentName];

        if (!stackComponent) continue;

        // Find the specific endpoint
        const endpoint = stackComponent.endpoints?.find(e => e.id === endpointId);
        if (!endpoint) continue;

        files.push(this.generateEndpointTest(
          componentName,
          endpoint,
          test.scenarios,
          outputDirectory
        ));
      }
    }

    // Generate integration tests
    if (workflow.testing.integration_tests) {
      for (const test of workflow.testing.integration_tests) {
        // Find which backend component this relates to
        const backendComponent = Object.entries(workflow.stack).find(([_, c]) => c.type === 'backend');
        if (backendComponent) {
          files.push(this.generateIntegrationTest(
            backendComponent[0],
            test,
            outputDirectory
          ));
        }
      }
    }

    // Generate test setup and helpers
    if (files.length > 0) {
      const backendComponent = Object.entries(workflow.stack).find(([_, c]) => c.type === 'backend');
      if (backendComponent) {
        files.push(this.generateTestSetup(backendComponent[0], outputDirectory));
        files.push(this.generateJestConfig(backendComponent[0], outputDirectory));
      }
    }

    return { files };
  }

  private generateEndpointTest(
    componentName: string,
    endpoint: any,
    scenarios: string[],
    outputDir: string
  ): GeneratedFile {
    const _testName = this.toTestName(endpoint.name);
    const routePath = endpoint.path.replace(/^\/api/, ''); // Remove /api prefix if present

    let content = `import request from 'supertest';
import { app } from '../index';
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';

describe('${endpoint.method} ${endpoint.path}', () => {
  beforeAll(async () => {
    // Setup test database, mocks, etc.
  });

  afterAll(async () => {
    // Cleanup
  });

`;

    // Generate test cases for each scenario
    for (const scenario of scenarios) {
      content += this.generateScenarioTest(scenario, endpoint, routePath);
    }

    content += `});
`;

    return {
      path: path.join(outputDir, componentName, 'src', 'routes', `${endpoint.id}.test.ts`),
      content,
      overwrite: true,
    };
  }

  private generateScenarioTest(scenario: string, endpoint: any, routePath: string): string {
    const lowerScenario = scenario.toLowerCase();
    const method = endpoint.method.toLowerCase();

    // Parse scenario to determine expected status code
    if (lowerScenario.includes('returns 200') || lowerScenario.includes('valid')) {
      return this.generateSuccessTest(endpoint, routePath, method);
    }

    if (lowerScenario.includes('returns 401') || lowerScenario.includes('unauthorized')) {
      return this.generateUnauthorizedTest(endpoint, routePath, method);
    }

    if (lowerScenario.includes('returns 400') || lowerScenario.includes('validation') || lowerScenario.includes('malformed')) {
      return this.generateValidationErrorTest(endpoint, routePath, method);
    }

    if (lowerScenario.includes('returns 404') || lowerScenario.includes('not found')) {
      return this.generateNotFoundTest(endpoint, routePath, method);
    }

    // Generic test
    return `  it('${scenario}', async () => {
    const response = await request(app)
      .${method}('${routePath}');

    // TODO: Implement test for: ${scenario}
    expect(response.status).toBeDefined();
  });

`;
  }

  private generateSuccessTest(endpoint: any, routePath: string, method: string): string {
    // Extract example request body from endpoint definition
    const requestBody = this.getExampleRequestBody(endpoint);
    const bodyCode = requestBody ? `\n      .send(${JSON.stringify(requestBody, null, 8)})` : '';

    const successResponse = endpoint.responses?.find((r: any) => r.status >= 200 && r.status < 300);
    const expectedFields = this.extractResponseFields(successResponse);

    return `  it('returns 200 with valid credentials', async () => {
    const response = await request(app)
      .${method}('${routePath}')${bodyCode};

    expect(response.status).toBe(${successResponse?.status || 200});
${expectedFields}  });

`;
  }

  private generateUnauthorizedTest(endpoint: any, routePath: string, method: string): string {
    const invalidBody = this.getInvalidRequestBody(endpoint);
    const bodyCode = invalidBody ? `\n      .send(${JSON.stringify(invalidBody, null, 8)})` : '';

    return `  it('returns 401 with invalid credentials', async () => {
    const response = await request(app)
      .${method}('${routePath}')${bodyCode};

    expect(response.status).toBe(401);
    expect(response.body).toHaveProperty('message');
  });

`;
  }

  private generateValidationErrorTest(endpoint: any, routePath: string, method: string): string {
    return `  it('returns 400 for malformed request', async () => {
    const response = await request(app)
      .${method}('${routePath}')
      .send({});

    expect(response.status).toBe(400);
    expect(response.body).toHaveProperty('message');
  });

`;
  }

  private generateNotFoundTest(endpoint: any, routePath: string, method: string): string {
    return `  it('returns 404 for non-existent resource', async () => {
    const response = await request(app)
      .${method}('${routePath}')
      .send({ email: 'nonexistent@example.com', password: 'test' });

    expect(response.status).toBe(404);
  });

`;
  }

  private generateIntegrationTest(
    componentName: string,
    test: any,
    outputDir: string
  ): GeneratedFile {
    const testName = this.toKebabCase(test.name);

    let content = `import request from 'supertest';
import { app } from '../../index';
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';

describe('${test.name}', () => {
  beforeAll(async () => {
    // Setup test environment
  });

  afterAll(async () => {
    // Cleanup
  });

  it('${test.description || test.name}', async () => {
`;

    // Generate steps
    for (const step of test.steps) {
      content += `    // ${step}\n`;
    }

    content += `
    // TODO: Implement integration test steps
    expect(true).toBe(true);
  });
});
`;

    return {
      path: path.join(outputDir, componentName, 'src', 'test', 'integration', `${testName}.test.ts`),
      content,
      overwrite: true,
    };
  }

  private generateTestSetup(componentName: string, outputDir: string): GeneratedFile {
    const content = `/**
 * Test setup and configuration
 */

// Mock environment variables
process.env.NODE_ENV = 'test';
process.env.DATABASE_URL = 'postgresql://test:test@localhost:5432/test';
process.env.JWT_SECRET = 'test-secret-key';

// Global test utilities
export const createTestUser = async () => {
  // TODO: Implement test user creation
  return {
    id: '123',
    email: 'test@example.com',
    name: 'Test User',
  };
};

export const generateTestToken = (userId: string) => {
  // TODO: Implement test JWT generation
  return 'test-jwt-token';
};
`;

    return {
      path: path.join(outputDir, componentName, 'src', 'test', 'setup.ts'),
      content,
      overwrite: true,
    };
  }

  private generateJestConfig(componentName: string, outputDir: string): GeneratedFile {
    const content = `module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
  transform: {
    '^.+\\\\.ts$': 'ts-jest',
  },
  coverageDirectory: 'coverage',
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.test.ts',
    '!src/**/*.spec.ts',
    '!src/test/**',
  ],
  setupFilesAfterEnv: ['<rootDir>/src/test/setup.ts'],
};
`;

    return {
      path: path.join(outputDir, componentName, 'jest.config.js'),
      content,
      overwrite: false,
    };
  }

  // Helper methods
  private getExampleRequestBody(endpoint: any): any {
    if (!endpoint.request?.body?.properties) return null;

    const body: any = {};
    for (const [key, prop] of Object.entries(endpoint.request.body.properties)) {
      const propSchema = prop as any;
      if (propSchema.example !== undefined) {
        body[key] = propSchema.example;
      } else if (propSchema.format === 'email') {
        body[key] = 'test@example.com';
      } else if (propSchema.format === 'password') {
        body[key] = 'password123';
      } else if (propSchema.type === 'string') {
        body[key] = 'test';
      }
    }
    return Object.keys(body).length > 0 ? body : null;
  }

  private getInvalidRequestBody(_endpoint: any): any {
    return { email: 'invalid@example.com', password: 'wrongpassword' };
  }

  private extractResponseFields(response: any): string {
    if (!response?.body?.properties) return '';

    const properties = Object.keys(response.body.properties);
    return properties
      .map(prop => `    expect(response.body).toHaveProperty('${prop}');`)
      .join('\n') + '\n';
  }

  private toTestName(name: string): string {
    return name.toLowerCase().replace(/\s+/g, '-');
  }

  private toKebabCase(str: string): string {
    return str.toLowerCase().replace(/\s+/g, '-');
  }
}
