import { Injectable, Logger } from '@nestjs/common';
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
import {TestDefinition,TestOptions,TestResult} from './interface/access-control.interface'

@Injectable()
export class BrokenAccessControlService {
  private readonly logger = new Logger(BrokenAccessControlService.name);
  
  // Common error patterns that indicate access was denied even with 200 status
  private commonErrorPatterns = [
    'access denied',
    'not authorized',
    'permission denied',
    'forbidden',
    'unauthorized',
    'not found',
    'invalid access',
    'login required',
    'sign in',
    'authentication required',
    'you don\'t have permission',
    'error',
    '401',
    '403',
    '404',
    'denied',
    'restricted',
    'page not found',
    'lost your way',
    'something went wrong',
    'hmm, we can\'t find that page',
    'sorry',
    'unavailable',
    'does not exist',
    'couldn\'t find'
  ];

  // Pattern that would indicate actual admin content on legitimate admin pages
  private adminContentPatterns = [
    'user management interface',
    'admin dashboard controls',
    'administrative panel',
    'administration console',
    'manage user accounts',
    'administrator controls',
    'system administration',
    'access control settings'
  ];
  
  private tests: TestDefinition[] = [
    // Administrative endpoints
    {
      endpoint: '/admin',
      method: 'GET',
      expected_access: false,
      severity: 'critical',
      description: 'Admin dashboard should not be accessible by unauthorized users',
      errorPatterns: this.commonErrorPatterns,
      accessPatterns: this.adminContentPatterns,
    },
    {
      endpoint: '/admin/users',
      method: 'GET',
      expected_access: false,
      severity: 'critical',
      description: 'User administration should not be accessible by unauthorized users',
      errorPatterns: this.commonErrorPatterns,
      accessPatterns: ['user list', 'manage users', 'user administration', 'user table', 'user id'],
    },
    
    // API endpoints
    {
      endpoint: '/api/users',
      method: 'GET',
      expected_access: false,
      severity: 'High',
      description: 'API users endpoint should be restricted to authorized users',
      errorPatterns: this.commonErrorPatterns,
      accessPatterns: ['user data', 'email', 'username', '"id":', '"users":', 'user list'],
    },
    {
      endpoint: '/api/users/1',
      method: 'GET',
      expected_access: false,
      severity: 'High',
      description: 'User details should not be accessible without proper authorization',
      errorPatterns: this.commonErrorPatterns,
      accessPatterns: ['email', 'username', 'password', 'user details', '"id":', '"user":'],
    },
    {
      endpoint: '/api/users',
      method: 'POST',
      expected_access: false,
      severity: 'critical',
      description: 'User creation should be restricted',
      payload: { username: 'test', password: 'test123' },
      errorPatterns: this.commonErrorPatterns,
      accessPatterns: ['user created', 'success', 'created', '201', '"id":', 'new user'],
    },
    
    // User accounts
    {
      endpoint: '/settings',
      method: 'GET',
      expected_access: false,
      severity: 'High',
      description: 'Settings endpoint should be restricted',
      errorPatterns: this.commonErrorPatterns,
      accessPatterns: ['settings', 'configuration', 'preferences', 'options'],
    },
    
    // Configuration and system
    {
      endpoint: '/api/config',
      method: 'GET',
      expected_access: false,
      severity: 'critical',
      description: 'API configuration endpoint should be restricted',
      errorPatterns: this.commonErrorPatterns,
      accessPatterns: ['config', 'configuration', 'settings', 'environment', 'api key', 'secret', 'password'],
    },
    {
      endpoint: '/api/logs',
      method: 'GET',
      expected_access: false,
      severity: 'Medium',
      description: 'Logs should be restricted to authorized users',
      errorPatterns: this.commonErrorPatterns,
      accessPatterns: ['logs', 'log entry', 'timestamp', 'error log', 'access log', 'time'],
    },
    // CSRF tests
    {
      endpoint: '/api/changepassword',
      method: 'POST',
      expected_access: false,
      severity: 'High',
      description: 'Password change should require CSRF token',
      payload: { newPassword: 'newpassword123' },
      errorPatterns: [...this.commonErrorPatterns, 'csrf', 'token', 'missing token'],
      accessPatterns: ['password changed', 'success', 'updated'],
    },
    
    // Directory traversal attempt
    {
      endpoint: '/../../../etc/passwd',
      method: 'GET',
      expected_access: false,
      severity: 'critical',
      description: 'Directory traversal should not be possible',
      errorPatterns: this.commonErrorPatterns,
      accessPatterns: ['root:', 'bin:', 'etc:', 'usr:', '/home/', 'passwd'],
    },
  ];

  // Analyze the response content to determine if access is allowed
  private analyzeResponseContent(
    response: AxiosResponse, 
    test: TestDefinition
  ): { isAccessible: boolean; details: string } {
    const contentType = response.headers['content-type'] || '';
    const responseBody = typeof response.data === 'string' 
      ? response.data 
      : JSON.stringify(response.data);
    
    // If we're looking at HTML, convert to lowercase for case-insensitive matching
    const bodyForAnalysis = contentType.includes('html') 
      ? responseBody.toLowerCase() 
      : responseBody;
    
    if (bodyForAnalysis.includes('page not found') || 
        bodyForAnalysis.includes('lost your way') ||
        bodyForAnalysis.includes('could\'t find') ||
        bodyForAnalysis.includes('not in our service') ||
        bodyForAnalysis.includes('doesn\'t exist')) {
      return {
        isAccessible: false,
        details: 'Response contains custom error page content'
      };
    }
    
    // Check for presence of login forms which often appear when access is denied
    if (bodyForAnalysis.includes('type="password"') || 
        bodyForAnalysis.includes('sign in') || 
        bodyForAnalysis.includes('login') ||
        bodyForAnalysis.includes('username') && bodyForAnalysis.includes('password')) {
      return {
        isAccessible: false,
        details: 'Response contains login form, suggesting access is restricted'
      };
    }
    
    // Check for common titles in HTML errors, even if they don't match exactly
    if (contentType.includes('html')) {
      const titleMatch = bodyForAnalysis.match(/<title>(.*?)<\/title>/i);
      const title = titleMatch ? titleMatch[1].toLowerCase() : '';
      
      if (title.includes('404') || 
          title.includes('error') || 
          title.includes('not found') || 
          title.includes('access denied') ||
          title.includes('not available') ||
          title.includes('restricted') ||
          title.includes('sorry')) {
        return { 
          isAccessible: false, 
          details: `Response contains error page title: "${titleMatch ? titleMatch[1] : 'No title'}"` 
        };
      }
    }
    
    // Check for error patterns in the response
    const errorPatterns = test.errorPatterns || this.commonErrorPatterns;
    const foundErrorPatterns = errorPatterns.filter(pattern => 
      bodyForAnalysis.includes(pattern.toLowerCase())
    );
    
    // Strong error indicators
    if (foundErrorPatterns.length > 0) {
      return { 
        isAccessible: false, 
        details: `Response contains error indicators: ${foundErrorPatterns.join(', ')}` 
      };
    }
    
    // For restricted endpoints, we need more concrete proof than just finding generic keywords
    // We should look for multiple access patterns and specific administrative content
    if (!test.expected_access) {
      // Check for specific access patterns that would indicate actual admin content
      const accessPatterns = test.accessPatterns || [];
      
      // Count the number of access patterns found
      const foundAccessPatterns = accessPatterns.filter(pattern => 
        bodyForAnalysis.includes(pattern.toLowerCase())
      );
      
      // For critical admin pages, we need stronger evidence - just finding the word "dashboard"
      // isn't enough when it might be part of normal content
      if (test.severity.toLowerCase() === 'critical') {
        // For admin endpoints, check for specific admin UI patterns
        if (test.endpoint.includes('/admin')) {
          // Look for clear admin UI indicators
          const strongAdminIndicators = [
            'user management',
            'add user',
            'delete user',
            'admin console',
            'administrator',
            'manage accounts',
            'account management'
          ];
          
          const foundStrongIndicators = strongAdminIndicators.filter(indicator =>
            bodyForAnalysis.includes(indicator.toLowerCase())
          );
          
          // If we found strong admin indicators, then it's a real admin page
          if (foundStrongIndicators.length > 0) {
            return {
              isAccessible: true,
              details: `Response contains strong admin indicators: ${foundStrongIndicators.join(', ')}`
            };
          }
          
          // If the page has a typical size for a UI page and contains multiple access patterns
          // but it's missing strong admin indicators, it's likely just a regular page with similar terms
          if (responseBody.length > 10000 && foundAccessPatterns.length > 0) {
            return {
              isAccessible: false,
              details: 'Response appears to be a regular page, not a true admin page'
            };
          }
        }
        
        // For config endpoints, look for actual configuration data
        if (test.endpoint.includes('/config') || test.endpoint.includes('/settings')) {
          // Check for actual configuration JSON or settings data
          const configIndicators = [
            '"api_key"',
            '"secret"',
            '"config":{',
            '"settings":{',
            '"environment":{',
            'configuration settings',
            'system settings'
          ];
          
          const foundConfigIndicators = configIndicators.filter(indicator =>
            bodyForAnalysis.includes(indicator.toLowerCase())
          );
          
          if (foundConfigIndicators.length > 0) {
            return {
              isAccessible: true,
              details: `Response contains actual configuration data: ${foundConfigIndicators.join(', ')}`
            };
          }
        }
        
        // For directory traversal, look for actual system file content
        if (test.endpoint.includes('etc/passwd')) {
          // Check for actual passwd file content structure, not just the word "passwd"
          if (bodyForAnalysis.includes('root:') && 
              (bodyForAnalysis.includes('/bin/') || bodyForAnalysis.includes('/home/'))) {
            return {
              isAccessible: true,
              details: 'Response contains actual passwd file content'
            };
          } else {
            return {
              isAccessible: false,
              details: 'Response does not contain passwd file structure'
            };
          }
        }
        
        // For endpoints needing stronger validation, require multiple patterns
        // and proper response size to reduce false positives
        if (foundAccessPatterns.length < 3 && test.severity.toLowerCase() === 'critical') {
          return {
            isAccessible: false,
            details: 'Not enough strong evidence of actual privileged content'
          };
        }
      }
    }
    
    // For public endpoints that should be accessible
    if (test.expected_access) {
      // Check for access patterns that would indicate the content is accessible
      const accessPatterns = test.accessPatterns || [];
      const foundAccessPatterns = accessPatterns.filter(pattern => 
        bodyForAnalysis.includes(pattern.toLowerCase())
      );
      
      // If we find actual access patterns and no error patterns, it's likely accessible
      if (foundAccessPatterns.length > 0) {
        return { 
          isAccessible: true,
          details: `Response contains expected content: ${foundAccessPatterns.join(', ')}` 
        };
      }
    }
    
    // Analyze response size - most error pages are small
    // But be cautious as some legitimate JSON responses can also be small
    const contentIsJSON = contentType.includes('json');
    const sizeThreshold = contentIsJSON ? 50 : 500;
    const isResponseTooSmall = responseBody.length < sizeThreshold;
    
    // If response is very small and not expected to be accessible, it's likely an error
    if (isResponseTooSmall && !test.expected_access && !contentIsJSON) {
      return { 
        isAccessible: false, 
        details: 'Response is too small to be a real content page' 
      };
    }
    
    // For public sites like Netflix, they return custom error pages that are actually full HTML
    // with site navigation etc., so we need to be careful with our detection
    if (response.status >= 200 && response.status < 400 && !test.expected_access) {
      // For critical security endpoints, assume Netflix and similar popular sites
      // have proper security and these are custom error pages unless proven otherwise
      if (test.severity.toLowerCase() === 'critical') {
        return {
          isAccessible: false,
          details: 'Popular site likely returning custom error page with 200 status'
        };
      }
    }
    
    // Default to status code-based decision if we couldn't determine from content
    const accessAllowed = response.status >= 200 && response.status < 400;
    return { 
      isAccessible: accessAllowed,
      details: `Determined by status code ${response.status}`
    };
  }

  async runAllTests(baseUrl: string, options: TestOptions = {}): Promise<TestResult[]> {
    this.logger.log(`Starting broken access control tests for URL: ${baseUrl}`);
    const results: TestResult[] = [];
    
    const defaultOptions: TestOptions = {
      timeout: 5000,
      headers: {
        'User-Agent': options.userAgent || 'OWASP-Security-Scanner/1.0',
      },
      followRedirects: true,
    };
    
    const testOptions = { ...defaultOptions, ...options };
    
    for (const test of this.tests) {
      try {
        const url = new URL(test.endpoint, baseUrl).toString();
        const startTime = Date.now();
        
        // Prepare request config
        const config: AxiosRequestConfig = {
          method: test.method,
          url,
          timeout: testOptions.timeout,
          validateStatus: () => true, // Accept any status code for analysis
          maxRedirects: testOptions.followRedirects ? 5 : 0,
          headers: {
            ...(testOptions.headers || {}),
            ...(test.headers || {}),
          },
        };
        
        // Add auth token if provided
        if (testOptions.authToken) {
          config.headers = {
            ...config.headers,
            'Authorization': `Bearer ${testOptions.authToken}`,
          };
        }
        
        // Add payload for non-GET requests
        if (test.method !== 'GET' && test.payload) {
          config.data = test.payload;
        }
        
        // Perform the request
        const response = await axios(config);
        const responseTime = Date.now() - startTime;
        
        // Analyze the response content instead of just looking at status code
        const { isAccessible, details: analysisDetails } = this.analyzeResponseContent(response, test);
        
        // Determine if the result indicates a vulnerability
        const isVulnerable = test.expected_access !== isAccessible;
        
        // Generate detailed explanation
        let details = '';
        if (isVulnerable) {
          if (test.expected_access) {
            details = `This endpoint should be accessible but appears to be restricted (${analysisDetails}), indicating a potential issue with legitimate access.`;
          } else {
            details = `This endpoint should be restricted but was accessible (${analysisDetails}), indicating a broken access control vulnerability.`;
          }
        } else {
          if (test.expected_access) {
            details = `This endpoint is correctly accessible (${analysisDetails}).`;
          } else {
            details = `This endpoint is correctly restricted (${analysisDetails}).`;
          }
        }
        
        results.push({
          endpoint: test.endpoint,
          method: test.method,
          success: isVulnerable, // success means vulnerability found
          severity: test.severity,
          description: test.description,
          statusCode: response.status,
          responseTime,
          details,
        });
      } catch (error) {
        this.logger.warn(`Error testing endpoint ${test.endpoint}: ${error.message}`);
        
        // For network errors, consider the endpoint inaccessible
        const accessAllowed = false;
        const isVulnerable = test.expected_access !== accessAllowed;
        
        results.push({
          endpoint: test.endpoint,
          method: test.method,
          success: isVulnerable,
          severity: test.severity,
          description: test.description,
          details: `Error during test: ${error.message}`,
        });
      }
    }
    
    this.logger.log(`Completed broken access control tests. Found ${results.filter(r => r.success).length} vulnerabilities.`);
    return results;
  }
  
  // Helper method to test custom endpoints
  async testCustomEndpoint(
    baseUrl: string, 
    endpoint: string, 
    method: string = 'GET',
    expected_access: boolean = false,
    options: TestOptions = {},
    payload?: any,
    errorPatterns?: string[],
    accessPatterns?: string[],
  ): Promise<TestResult> {
    try {
      const url = new URL(endpoint, baseUrl).toString();
      const config: AxiosRequestConfig = {
        method,
        url,
        timeout: options.timeout || 5000,
        validateStatus: () => true,
        headers: options.headers || {},
      };
      
      if (options.authToken) {
        config.headers = {
          ...config.headers,
          'Authorization': `Bearer ${options.authToken}`,
        };
      }
      
      if (method !== 'GET' && payload) {
        config.data = payload;
      }
      
      const response = await axios(config);
      
      // Create a test definition with the provided patterns
      const testDef: TestDefinition = {
        endpoint,
        method,
        expected_access,
        severity: 'custom',
        description: `Custom test for ${method} ${endpoint}`,
        errorPatterns: errorPatterns || this.commonErrorPatterns,
        accessPatterns: accessPatterns || [],
      };
      
      // Analyze the response content
      const { isAccessible, details: analysisDetails } = this.analyzeResponseContent(response, testDef);
      const isVulnerable = expected_access !== isAccessible;
      
      return {
        endpoint,
        method,
        success: isVulnerable,
        severity: 'custom',
        description: `Custom test for ${method} ${endpoint}`,
        statusCode: response.status,
        details: isVulnerable 
          ? `Access control issue detected: ${expected_access ? 'should be accessible' : 'should be restricted'} but ${isAccessible ? 'was accessible' : 'was not accessible'} (${analysisDetails})`
          : `No access control issue detected (${analysisDetails})`,
      };
    } catch (error) {
      return {
        endpoint,
        method,
        success: expected_access, // If we expected access but got an error, that's a vulnerability
        severity: 'custom',
        description: `Custom test for ${method} ${endpoint}`,
        details: `Error during test: ${error.message}`,
      };
    }
  }
  
  // Add a method to validate and format the results for reporting
  formatResults(results: TestResult[]): {
    url: string;
    summary: {
      totalTests: number;
      vulnerabilitiesFound: number;
      secureEndpoints: number;
      criticalIssues: number;
      highIssues: number;
      mediumIssues: number;
      lowIssues: number;
    };
    vulnerabilities: {
      critical: TestResult[];
      high: TestResult[];
      medium: TestResult[];
      low: TestResult[];
    };
    secure: TestResult[];
  } {
    // Filter successful vulnerabilities
    const vulnerabilities = results.filter(r => r.success);
    const secure = results.filter(r => !r.success);
    
    // Group vulnerabilities by severity
    const critical = vulnerabilities.filter(v => v.severity.toLowerCase() === 'critical');
    const high = vulnerabilities.filter(v => v.severity.toLowerCase() === 'high');
    const medium = vulnerabilities.filter(v => v.severity.toLowerCase() === 'medium');
    const low = vulnerabilities.filter(v => v.severity.toLowerCase() === 'low');
    
    return {
      url: 'target-url', // This should be dynamically set
      summary: {
        totalTests: results.length,
        vulnerabilitiesFound: vulnerabilities.length,
        secureEndpoints: secure.length,
        criticalIssues: critical.length,
        highIssues: high.length,
        mediumIssues: medium.length,
        lowIssues: low.length,
      },
      vulnerabilities: {
        critical,
        high,
        medium,
        low,
      },
      secure,
    };
  }
}