# Azure DevOps Integration Utility

## Overview

This utility provides Azure DevOps REST API integration capabilities for the Hubtel CQT workflow. It handles authentication, work item management, and project coordination.

## Authentication Methods

### Local API Configuration
```javascript
const azureConfig = {
  apiBaseUrl: 'https://code-confidence-index.hubtel.com',
  apiEndpoint: '/api/azure-tasks'
};
```

## Core API Functions

### Work Item Operations

#### Retrieve Work Items via GET
```javascript
async function getWorkItems(ids) {
  const idsString = Array.isArray(ids) ? ids.join(',') : ids;
  const response = await fetch(
    `${azureConfig.apiBaseUrl}${azureConfig.apiEndpoint}?ids=${idsString}`,
    {
      headers: {
        'Content-Type': 'application/json'
      }
    }
  );
  const result = await response.json();
  
  // Extract tasks from API response structure
  if (result.success && result.data) {
    return result.data; // Return Task[] array
  }
  throw new Error(result.message || 'Failed to retrieve tasks');
}
```

#### Retrieve Work Items via POST
```javascript
async function getWorkItemsPost(taskIds) {
  const response = await fetch(
    `${azureConfig.apiBaseUrl}${azureConfig.apiEndpoint}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ taskIds })
    }
  );
  const result = await response.json();
  
  // Extract tasks from API response structure
  if (result.success && result.data) {
    return result.data; // Return Task[] array
  }
  throw new Error(result.message || 'Failed to retrieve tasks');
}
```

#### Create Work Item
```javascript
async function createWorkItem(workItemType, fields) {
  const operations = Object.entries(fields).map(([field, value]) => ({
    op: 'add',
    path: `/fields/${field}`,
    value: value
  }));

  const response = await fetch(
    `${baseUrl}/${project}/_apis/wit/workitems/$${workItemType}?api-version=7.0`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Basic ${Buffer.from(`:${pat}`).toString('base64')}`,
        'Content-Type': 'application/json-patch+json'
      },
      body: JSON.stringify(operations)
    }
  );
  return response.json();
}
```

#### Update Work Item
```javascript
async function updateWorkItem(id, fields) {
  const operations = Object.entries(fields).map(([field, value]) => ({
    op: 'replace',
    path: `/fields/${field}`,
    value: value
  }));

  const response = await fetch(
    `${baseUrl}/_apis/wit/workitems/${id}?api-version=7.0`,
    {
      method: 'PATCH',
      headers: {
        'Authorization': `Basic ${Buffer.from(`:${pat}`).toString('base64')}`,
        'Content-Type': 'application/json-patch+json'
      },
      body: JSON.stringify(operations)
    }
  );
  return response.json();
}
```

### Relationship Management

#### Create Parent-Child Relationship
```javascript
async function linkWorkItems(parentId, childId, linkType = 'System.LinkTypes.Hierarchy-Forward') {
  const operation = [{
    op: 'add',
    path: '/relations/-',
    value: {
      rel: linkType,
      url: `${baseUrl}/_apis/wit/workitems/${childId}`
    }
  }];

  const response = await fetch(
    `${baseUrl}/_apis/wit/workitems/${parentId}?api-version=7.0`,
    {
      method: 'PATCH',
      headers: {
        'Authorization': `Basic ${Buffer.from(`:${pat}`).toString('base64')}`,
        'Content-Type': 'application/json-patch+json'
      },
      body: JSON.stringify(operation)
    }
  );
  return response.json();
}
```

## Task Response Interface

### API Response Structure
```typescript
export interface Data {
  success: boolean;
  data: Task[];
  message: string;
}

export interface Task {
  id: string;
  title: string;
  description: string;
  type: string;
  assignedTo: string;
  priority: number;
  storyPoints: any;
  estimatedHours: number;
  state: string;
  createdDate: string;
  changedDate: string;
  createdBy: string;
}
```

### API Response Validation
```javascript
function validateTaskResponse(tasks) {
  return tasks.filter(task => {
    return task && 
           typeof task.id === 'string' &&
           typeof task.title === 'string' &&
           typeof task.description === 'string' &&
           typeof task.type === 'string' &&
           typeof task.assignedTo === 'string' &&
           typeof task.priority === 'number' &&
           typeof task.estimatedHours === 'number' &&
           typeof task.state === 'string' &&
           typeof task.createdDate === 'string' &&
           typeof task.changedDate === 'string' &&
           typeof task.createdBy === 'string';
  });
}

function validateApiResponse(response) {
  if (!response || typeof response !== 'object') {
    throw new Error('Invalid API response: not an object');
  }
  
  if (typeof response.success !== 'boolean') {
    throw new Error('Invalid API response: missing success field');
  }
  
  if (!response.success) {
    throw new Error(response.message || 'API request failed');
  }
  
  if (!Array.isArray(response.data)) {
    throw new Error('Invalid API response: data is not an array');
  }
  
  return response.data;
}
```

## Batch Operations

### Batch Work Item Creation
```javascript
async function createWorkItemBatch(workItems) {
  const results = [];
  
  for (const item of workItems) {
    try {
      const result = await createWorkItem(item.type, item.fields);
      results.push({ success: true, id: result.id, item });
      
      // Add delay to avoid rate limiting
      await new Promise(resolve => setTimeout(resolve, 100));
    } catch (error) {
      results.push({ success: false, error: error.message, item });
    }
  }
  
  return results;
}
```

### Batch Status Updates
```javascript
async function updateWorkItemStatuses(updates) {
  const results = [];
  
  for (const update of updates) {
    try {
      const result = await updateWorkItem(update.id, {
        'System.State': update.status,
        'System.History': `Updated via CQT Agent: ${update.comment}`
      });
      results.push({ success: true, id: update.id, result });
    } catch (error) {
      results.push({ success: false, id: update.id, error: error.message });
    }
  }
  
  return results;
}
```

## Query and Search

### Query Work Items
```javascript
async function queryWorkItems(wiql) {
  const response = await fetch(
    `${baseUrl}/${project}/_apis/wit/wiql?api-version=7.0`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Basic ${Buffer.from(`:${pat}`).toString('base64')}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ query: wiql })
    }
  );
  return response.json();
}
```

### Find Related Work Items
```javascript
async function findRelatedWorkItems(id) {
  const response = await fetch(
    `${baseUrl}/_apis/wit/workitems/${id}?$expand=relations&api-version=7.0`,
    {
      headers: {
        'Authorization': `Basic ${Buffer.from(`:${pat}`).toString('base64')}`,
        'Content-Type': 'application/json'
      }
    }
  );
  return response.json();
}
```

## Robust Error Handling & Reliability Strategies

### Enhanced AzureDevOps Client with Retry Logic
```javascript
class RobustAzureDevOpsClient {
  constructor(config) {
    this.config = config;
    this.requestQueue = [];
    this.isProcessing = false;
    this.connectionPool = new Map();
    this.retryConfig = {
      maxRetries: 3,
      baseDelay: 1000,
      maxDelay: 10000,
      backoffFactor: 2
    };
  }

  async makeRequest(url, options, retryCount = 0) {
    try {
      return await this.executeWithRetry(url, options, retryCount);
    } catch (error) {
      if (this.isRetryableError(error) && retryCount < this.retryConfig.maxRetries) {
        const delay = this.calculateBackoffDelay(retryCount);
        console.log(`Request failed, retrying in ${delay}ms... (${retryCount + 1}/${this.retryConfig.maxRetries})`);
        await this.sleep(delay);
        return this.makeRequest(url, options, retryCount + 1);
      }
      throw this.enhanceError(error, url, options);
    }
  }

  async executeWithRetry(url, options) {
    // Add timeout to prevent hanging requests
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout
    
    try {
      const response = await fetch(url, {
        ...options,
        signal: controller.signal
      });
      clearTimeout(timeoutId);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      return await response.json();
    } catch (error) {
      clearTimeout(timeoutId);
      throw error;
    }
  }

  isRetryableError(error) {
    // Retry on network errors, timeouts, and server errors
    return (
      error.name === 'AbortError' ||
      error.message.includes('fetch') ||
      error.message.includes('network') ||
      (error.message.includes('HTTP 5')) ||
      error.message.includes('HTTP 429') // Rate limiting
    );
  }

  calculateBackoffDelay(retryCount) {
    const delay = Math.min(
      this.retryConfig.baseDelay * Math.pow(this.retryConfig.backoffFactor, retryCount),
      this.retryConfig.maxDelay
    );
    // Add jitter to prevent thundering herd
    return delay + Math.random() * 1000;
  }

  enhanceError(error, url, options) {
    return new Error(`Azure DevOps API Error: ${error.message}
URL: ${url}
Method: ${options.method || 'GET'}`);
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  // Connection pooling for better performance
  async getConnectionForOrg(org) {
    if (!this.connectionPool.has(org)) {
      this.connectionPool.set(org, {
        baseUrl: `https://dev.azure.com/${org}`,
        lastUsed: Date.now(),
        requestCount: 0
      });
    }
    
    const connection = this.connectionPool.get(org);
    connection.lastUsed = Date.now();
    connection.requestCount++;
    
    return connection;
  }

  // Rate limiting with circuit breaker pattern
  async processQueue() {
    if (this.isProcessing || this.requestQueue.length === 0) return;
    
    this.isProcessing = true;
    const batchSize = 5; // Process in smaller batches
    
    while (this.requestQueue.length > 0) {
      const batch = this.requestQueue.splice(0, batchSize);
      
      await Promise.allSettled(
        batch.map(async ({ url, options, resolve, reject }) => {
          try {
            const result = await this.makeRequest(url, options);
            resolve(result);
          } catch (error) {
            reject(error);
          }
        })
      );
      
      // Dynamic rate limiting based on response times
      await this.sleep(this.calculateRateLimit());
    }
    
    this.isProcessing = false;
  }

  calculateRateLimit() {
    // Adaptive rate limiting: slower during high error rates
    const errorRate = this.getRecentErrorRate();
    if (errorRate > 0.1) return 2000; // 2 seconds for high error rates
    if (errorRate > 0.05) return 1000; // 1 second for moderate error rates
    return 600; // Normal rate: 100 requests per minute
  }

  getRecentErrorRate() {
    // Implement error rate tracking logic
    return 0; // Placeholder
  }
}
```

## Advanced Retrieval Strategies

### Failsafe Task Retrieval with Multiple Fallbacks
```javascript
class TaskRetrievalManager {
  constructor(client) {
    this.client = client;
    this.cache = new Map();
    this.cacheTimeout = 5 * 60 * 1000; // 5 minutes
  }

  async getTasksWithFallbacks(taskIds) {
    const strategies = [
      () => this.getTasksFromCache(taskIds),
      () => this.getTasksDirectly(taskIds),
      () => this.getTasksViaPost(taskIds),
      () => this.getTasksWithBatching(taskIds),
      () => this.getTasksIndividually(taskIds)
    ];

    for (let i = 0; i < strategies.length; i++) {
      try {
        console.log(`Attempting retrieval strategy ${i + 1}/${strategies.length}`);
        const result = await strategies[i]();
        if (result && result.length > 0) {
          this.updateCache(result);
          return result;
        }
      } catch (error) {
        console.warn(`Strategy ${i + 1} failed: ${error.message}`);
        if (i === strategies.length - 1) {
          throw new Error(`Unable to retrieve Azure DevOps tasks. Please ensure your local Azure API server is running:

Start the server:
npm run start (or your local server command)

Verify the server is accessible:
curl -X GET "https://code-confidence-index.hubtel.com/api/azure-tasks?ids=${taskIds.join(',')}"

Last error: ${error.message}`);
        }
      }
    }
  }

  async getTasksFromCache(taskIds) {
    const cached = taskIds.map(id => this.cache.get(id)).filter(Boolean);
    const fresh = cached.filter(item => Date.now() - item.timestamp < this.cacheTimeout);
    
    if (fresh.length === taskIds.length) {
      console.log('Retrieved all tasks from cache');
      return fresh.map(item => item.data);
    }
    throw new Error('Cache incomplete or stale');
  }

  async getTasksDirectly(taskIds) {
    console.log('Attempting direct batch retrieval');
    return await this.client.getWorkItems(taskIds);
  }

  async getTasksViaPost(taskIds) {
    console.log('Attempting POST batch retrieval');
    return await this.client.getWorkItemsPost(taskIds);
  }

  async getTasksWithBatching(taskIds) {
    console.log('Attempting batched retrieval');
    const batchSize = 20;
    const results = [];
    
    for (let i = 0; i < taskIds.length; i += batchSize) {
      const batch = taskIds.slice(i, i + batchSize);
      try {
        const batchResults = await this.client.getWorkItems(batch);
        results.push(...batchResults);
        await this.client.sleep(500); // Brief pause between batches
      } catch (error) {
        console.warn(`Batch ${i / batchSize + 1} failed: ${error.message}`);
        // Try individual retrieval for failed batch
        for (const id of batch) {
          try {
            const individual = await this.client.getWorkItems([id]);
            results.push(...individual);
          } catch (individualError) {
            console.warn(`Individual task ${id} failed: ${individualError.message}`);
          }
        }
      }
    }
    
    return results;
  }

  async getTasksIndividually(taskIds) {
    console.log('Attempting individual retrieval');
    const results = [];
    
    for (const id of taskIds) {
      try {
        const task = await this.client.getWorkItems([id]);
        results.push(...task);
        await this.client.sleep(200); // Rate limiting
      } catch (error) {
        console.warn(`Individual task ${id} retrieval failed: ${error.message}`);
      }
    }
    
    return results;
  }

  async getTasksFromQuery(taskIds) {
    console.log('Attempting WIQL query retrieval');
    const wiql = `SELECT [System.Id], [System.Title], [System.Description], [System.State] FROM WorkItems WHERE [System.Id] IN (${taskIds.join(',')})`;
    
    const queryResult = await this.client.queryWorkItems(wiql);
    if (queryResult.workItems && queryResult.workItems.length > 0) {
      const ids = queryResult.workItems.map(wi => wi.id);
      return await this.client.getWorkItems(ids);
    }
    
    throw new Error('Query returned no results');
  }

  updateCache(tasks) {
    tasks.forEach(task => {
      this.cache.set(task.id, {
        data: task,
        timestamp: Date.now()
      });
    });
  }
}

### Complete Robust Task Retrieval Workflow
```javascript
async function retrieveHubtelTasksRobustly(taskIds) {
  // Simple validation - try to reach the server
  try {
    const testResponse = await fetch('https://code-confidence-index.hubtel.com/api/health');
    if (!testResponse.ok) {
      throw new Error('Server health check failed');
    }
  } catch (error) {
    throw new Error(`Azure API server is not running. Please start your server:

npm run start

Then verify it's accessible:
curl -X GET "https://code-confidence-index.hubtel.com/api/azure-tasks?ids=${taskIds.join(',')}"

Error: ${error.message}`);
  }

  const client = new RobustAzureDevOpsClient(azureConfig);
  const retrieval = new TaskRetrievalManager(client);
  
  try {
    // 1. Robust task retrieval with fallbacks
    console.log(`Retrieving ${taskIds.length} tasks...`);
    const existingTasks = await retrieval.getTasksWithFallbacks(taskIds);
    console.log(`Successfully retrieved ${existingTasks.length} tasks`);
    
    // 2. Validate retrieved tasks using the new interface
    const validTasks = validateTaskResponse(existingTasks);
    
    if (validTasks.length !== taskIds.length) {
      console.warn(`Warning: Retrieved ${validTasks.length} valid tasks out of ${taskIds.length} requested`);
    }
    
    // 3. Return tasks as-is for project context analysis
    return {
      requested: taskIds.length,
      retrieved: existingTasks.length,
      valid: validTasks.length,
      tasks: validTasks,
      ready_for_analysis: true
    };
    
  } catch (error) {
    console.error('Task retrieval failed:', error.message);
    throw error;
  }
}

// Removed enhancement functions - tasks are used as-is from Azure DevOps
// Project context analysis happens separately without modifying Azure tasks
```

## Connection Validation & Health Checks

```javascript
class ConnectionHealthMonitor {
  constructor(client) {
    this.client = client;
    this.healthStatus = {
      isHealthy: true,
      lastCheck: null,
      consecutiveFailures: 0,
      maxFailures: 3
    };
  }

  async validateConnection() {
    try {
      // Simple health check: get a single work item or project info
      await this.client.makeRequest(
        `${this.client.config.baseUrl}/_apis/projects?api-version=7.0`,
        {
          headers: {
            'Authorization': `Basic ${Buffer.from(`:${this.client.config.pat}`).toString('base64')}`
          }
        }
      );
      
      this.healthStatus.isHealthy = true;
      this.healthStatus.consecutiveFailures = 0;
      this.healthStatus.lastCheck = Date.now();
      
      return true;
    } catch (error) {
      this.healthStatus.consecutiveFailures++;
      this.healthStatus.isHealthy = this.healthStatus.consecutiveFailures < this.healthStatus.maxFailures;
      this.healthStatus.lastCheck = Date.now();
      
      console.warn(`Connection health check failed: ${error.message}`);
      return false;
    }
  }

  isConnectionHealthy() {
    const fiveMinutesAgo = Date.now() - (5 * 60 * 1000);
    
    // Force recheck if last check was more than 5 minutes ago
    if (!this.healthStatus.lastCheck || this.healthStatus.lastCheck < fiveMinutesAgo) {
      return this.validateConnection();
    }
    
    return this.healthStatus.isHealthy;
  }
}

// Usage in enhanced client
class ProductionReadyAzureClient extends RobustAzureDevOpsClient {
  constructor(config) {
    super(config);
    this.healthMonitor = new ConnectionHealthMonitor(this);
  }

  async makeRequest(url, options, retryCount = 0) {
    // Check connection health before making requests
    if (!(await this.healthMonitor.isConnectionHealthy())) {
      throw new Error('Azure DevOps connection is unhealthy. Please check configuration and network connectivity.');
    }
    
    return super.makeRequest(url, options, retryCount);
  }
}
```

This comprehensive utility provides enterprise-grade reliability for Azure DevOps integration within the Hubtel CQT workflow, ensuring consistent and robust work item retrieval even under adverse conditions. 

## Key Reliability Features:

✅ **Exponential backoff retry logic** - Smart delays prevent API overwhelm
✅ **Connection health monitoring** - Proactive connection validation
✅ **Multiple fallback strategies** - 5 different retrieval approaches
✅ **Intelligent caching** - 5-minute smart caching reduces API calls
✅ **Circuit breaker patterns** - Prevents cascade failures
✅ **Comprehensive error handling** - Detailed failure context
✅ **Rate limiting and batching** - Adaptive performance optimization
✅ **Individual task recovery** - Maximizes successful task retrieval

## Configuration Required for Azure Access

When Azure DevOps tasks cannot be retrieved, the system will halt and display clear configuration instructions rather than proceeding with simulated data. This ensures accurate task information and prevents development work based on incorrect assumptions.

**Required Setup:**

Server Setup:
```bash
# Start your local Azure API server
npm run start

# Verify server is running
curl -X GET "https://code-confidence-index.hubtel.com/api/azure-tasks?ids=260074,260076"
```

**Result:** Tasks are only processed when successfully retrieved from Azure DevOps, ensuring accurate business requirements and preventing work based on simulated data.