import simpleGit, { SimpleGit, CommitResult, PullResult, LogResult } from 'simple-git';
import { EventEmitter } from 'events';
import { stateMachineService } from './stateMachine';
import { webhookService } from './webhookService';
import { getAuthenticatedClient } from '../../scripts/utils/directus-client';

// Import Directus SDK functions
const directusSdk = require('@directus/sdk');
const readItems = directusSdk.readItems as any;
const updateItem = directusSdk.updateItem as any;

/**
 * Git Event Types
 */
export enum GitEventType {
  COMMIT = 'commit',
  PUSH = 'push',
  PULL = 'pull',
  BRANCH_CREATE = 'branch_create',
  BRANCH_DELETE = 'branch_delete',
  BRANCH_CHECKOUT = 'branch_checkout',
  MERGE = 'merge',
  TAG = 'tag',
  PR_OPEN = 'pr_open',
  PR_CLOSE = 'pr_close',
  PR_MERGE = 'pr_merge'
}

/**
 * Git Event Configuration
 */
export interface GitEventConfig {
  event: GitEventType;
  pattern?: RegExp;
  action: string; // Status transition action
  conditions?: {
    branch?: string | RegExp;
    author?: string;
    message?: RegExp;
  };
  metadata?: Record<string, any>;
}

/**
 * Task-Git Mapping
 */
export interface TaskGitMapping {
  taskId: string;
  branch?: string;
  commitPrefix?: string;
  prNumber?: number;
  issueNumber?: number;
}

/**
 * Git Event Handler Service
 * Integrates Git operations with task status workflow
 */
export class GitEventHandler extends EventEmitter {
  private git: SimpleGit;
  private client: any;
  private repoPath: string;
  private eventConfigs: GitEventConfig[] = [];
  private taskMappings: Map<string, TaskGitMapping> = new Map();
  private watchInterval: NodeJS.Timeout | null = null;
  private lastCommitHash: string = '';

  constructor(repoPath: string = process.cwd()) {
    super();
    this.repoPath = repoPath;
    this.git = simpleGit(repoPath);
    this.initializeClient();
    this.loadEventConfigs();
    this.loadTaskMappings();
  }

  private async initializeClient() {
    this.client = await getAuthenticatedClient();
  }

  /**
   * Load event configurations from database or config
   */
  private async loadEventConfigs() {
    // Default configurations
    this.eventConfigs = [
      {
        event: GitEventType.COMMIT,
        pattern: /^feat(\(.+\))?:/,
        action: 'START',
        conditions: {
          branch: /^(feature|develop)/
        }
      },
      {
        event: GitEventType.COMMIT,
        pattern: /^fix(\(.+\))?:/,
        action: 'START',
        conditions: {
          branch: /^(hotfix|bugfix)/
        }
      },
      {
        event: GitEventType.COMMIT,
        pattern: /^test(\(.+\))?:/,
        action: 'REVIEW',
        conditions: {
          message: /\bpasses?\b/i
        }
      },
      {
        event: GitEventType.COMMIT,
        pattern: /^done(\(.+\))?:/,
        action: 'COMPLETE'
      },
      {
        event: GitEventType.PR_MERGE,
        action: 'APPROVE'
      },
      {
        event: GitEventType.BRANCH_DELETE,
        action: 'ARCHIVE',
        conditions: {
          branch: /^feature\//
        }
      }
    ];

    // Load custom configurations from database if available
    if (this.client) {
      try {
        const customConfigs = await this.client.request(
          readItems('git_event_configs', {
            filter: { active: { _eq: true } }
          })
        );
        
        if (customConfigs.length > 0) {
          this.eventConfigs = [...this.eventConfigs, ...customConfigs];
        }
      } catch (error) {
        console.log('Using default Git event configurations');
      }
    }
  }

  /**
   * Load task-git mappings from database
   */
  private async loadTaskMappings() {
    if (!this.client) await this.initializeClient();

    try {
      const mappings = await this.client.request(
        readItems('task_git_mappings', {
          filter: { active: { _eq: true } }
        })
      );

      mappings.forEach((mapping: TaskGitMapping) => {
        this.taskMappings.set(mapping.taskId, mapping);
      });
    } catch (error) {
      console.log('No task-git mappings found');
    }
  }

  /**
   * Start watching for Git events
   */
  async startWatching(intervalMs: number = 5000): Promise<void> {
    console.log(`Starting Git event watcher (interval: ${intervalMs}ms)`);

    // Get initial commit hash
    const log = await this.git.log({ maxCount: 1 });
    this.lastCommitHash = log.latest?.hash || '';

    // Set up polling interval
    this.watchInterval = setInterval(async () => {
      await this.checkForNewCommits();
    }, intervalMs);

    // Also set up branch watching
    await this.setupBranchWatcher();
  }

  /**
   * Stop watching for Git events
   */
  stopWatching(): void {
    if (this.watchInterval) {
      clearInterval(this.watchInterval);
      this.watchInterval = null;
      console.log('Stopped Git event watcher');
    }
  }

  /**
   * Check for new commits
   */
  private async checkForNewCommits(): Promise<void> {
    try {
      const log = await this.git.log({ maxCount: 10 });
      
      if (log.latest && log.latest.hash !== this.lastCommitHash) {
        // Process new commits
        const newCommits = [];
        for (const commit of log.all) {
          if (commit.hash === this.lastCommitHash) break;
          newCommits.push(commit);
        }

        // Process in reverse order (oldest first)
        for (const commit of newCommits.reverse()) {
          await this.processCommit(commit);
        }

        this.lastCommitHash = log.latest.hash;
      }
    } catch (error) {
      console.error('Error checking for new commits:', error);
    }
  }

  /**
   * Process a Git commit
   */
  private async processCommit(commit: any): Promise<void> {
    console.log(`Processing commit: ${commit.hash.substring(0, 7)} - ${commit.message}`);

    // Extract task ID from commit message
    const taskId = this.extractTaskId(commit.message);
    if (!taskId) {
      console.log('No task ID found in commit message');
      return;
    }

    // Find matching event configuration
    const config = this.findMatchingConfig(GitEventType.COMMIT, commit);
    if (!config) {
      console.log('No matching Git event configuration');
      return;
    }

    // Trigger status transition
    await this.triggerStatusTransition(taskId, config, commit);
  }

  /**
   * Extract task ID from commit message
   */
  private extractTaskId(message: string): string | null {
    // Look for patterns like [TASK-123], #123, or task:123
    const patterns = [
      /\[TASK-(\d+)\]/i,
      /#(\d+)/,
      /task:\s*(\d+)/i,
      /\(task\s+(\d+)\)/i
    ];

    for (const pattern of patterns) {
      const match = message.match(pattern);
      if (match) {
        return match[1];
      }
    }

    return null;
  }

  /**
   * Find matching event configuration
   */
  private findMatchingConfig(eventType: GitEventType, data: any): GitEventConfig | null {
    for (const config of this.eventConfigs) {
      if (config.event !== eventType) continue;

      // Check pattern
      if (config.pattern && !config.pattern.test(data.message)) continue;

      // Check conditions
      if (config.conditions) {
        const currentBranch = data.branch || this.getCurrentBranch();
        
        if (config.conditions.branch) {
          const branchPattern = config.conditions.branch instanceof RegExp
            ? config.conditions.branch
            : new RegExp(config.conditions.branch);
          
          if (!branchPattern.test(currentBranch)) continue;
        }

        if (config.conditions.author && data.author !== config.conditions.author) {
          continue;
        }

        if (config.conditions.message) {
          if (!config.conditions.message.test(data.message)) continue;
        }
      }

      return config;
    }

    return null;
  }

  /**
   * Get current branch name
   */
  private async getCurrentBranch(): Promise<string> {
    try {
      const branch = await this.git.branch();
      return branch.current;
    } catch (error) {
      return 'main';
    }
  }

  /**
   * Trigger status transition based on Git event
   */
  private async triggerStatusTransition(
    taskId: string,
    config: GitEventConfig,
    gitData: any
  ): Promise<void> {
    try {
      // Create transition event
      const event: any = {
        type: config.action,
        metadata: {
          ...config.metadata,
          gitCommit: gitData.hash,
          gitAuthor: gitData.author_name,
          gitMessage: gitData.message,
          gitBranch: await this.getCurrentBranch()
        }
      };

      // Trigger state machine transition
      await stateMachineService.transition(taskId, event);

      // Trigger webhooks
      await webhookService.triggerWebhooks(`git.${config.event}`, taskId, {
        currentStatus: config.action.toLowerCase(),
        task: { id: taskId },
        metadata: event.metadata
      });

      // Emit event
      this.emit('statusTransition', {
        taskId,
        event: config.event,
        action: config.action,
        gitData
      });

      console.log(`Triggered ${config.action} for task ${taskId} based on Git ${config.event}`);
    } catch (error) {
      console.error(`Failed to trigger status transition: ${error}`);
    }
  }

  /**
   * Set up branch watcher
   */
  private async setupBranchWatcher(): Promise<void> {
    let currentBranch = await this.getCurrentBranch();

    setInterval(async () => {
      const newBranch = await this.getCurrentBranch();
      
      if (newBranch !== currentBranch) {
        await this.handleBranchChange(currentBranch, newBranch);
        currentBranch = newBranch;
      }
    }, 2000);
  }

  /**
   * Handle branch change
   */
  private async handleBranchChange(oldBranch: string, newBranch: string): Promise<void> {
    console.log(`Branch changed: ${oldBranch} -> ${newBranch}`);

    // Extract task ID from branch name
    const taskId = this.extractTaskIdFromBranch(newBranch);
    if (!taskId) return;

    // Find configuration for branch checkout
    const config = this.eventConfigs.find(
      c => c.event === GitEventType.BRANCH_CHECKOUT
    );

    if (config) {
      await this.triggerStatusTransition(taskId, config, {
        oldBranch,
        newBranch
      });
    }

    this.emit('branchChange', { oldBranch, newBranch, taskId });
  }

  /**
   * Extract task ID from branch name
   */
  private extractTaskIdFromBranch(branchName: string): string | null {
    // Look for patterns like feature/TASK-123, task-123, etc.
    const patterns = [
      /TASK-(\d+)/i,
      /task-(\d+)/i,
      /^(\d+)-/
    ];

    for (const pattern of patterns) {
      const match = branchName.match(pattern);
      if (match) {
        return match[1];
      }
    }

    return null;
  }

  /**
   * Handle pull request events (webhook receiver)
   */
  async handlePullRequestEvent(event: any): Promise<void> {
    const { action, number, pull_request } = event;
    
    // Extract task ID from PR title or body
    const taskId = this.extractTaskId(pull_request.title) || 
                   this.extractTaskId(pull_request.body);
    
    if (!taskId) {
      console.log('No task ID found in pull request');
      return;
    }

    let gitEventType: GitEventType;
    switch (action) {
      case 'opened':
        gitEventType = GitEventType.PR_OPEN;
        break;
      case 'closed':
        gitEventType = pull_request.merged 
          ? GitEventType.PR_MERGE 
          : GitEventType.PR_CLOSE;
        break;
      default:
        return;
    }

    const config = this.eventConfigs.find(c => c.event === gitEventType);
    if (config) {
      await this.triggerStatusTransition(taskId, config, {
        prNumber: number,
        prTitle: pull_request.title,
        prAuthor: pull_request.user.login,
        merged: pull_request.merged
      });
    }
  }

  /**
   * Create branch for task
   */
  async createTaskBranch(taskId: string, taskTitle: string): Promise<string> {
    const branchName = `task-${taskId}-${taskTitle.toLowerCase().replace(/\s+/g, '-').substring(0, 30)}`;
    
    try {
      await this.git.checkoutLocalBranch(branchName);
      
      // Store mapping
      this.taskMappings.set(taskId, {
        taskId,
        branch: branchName
      });

      // Save to database
      if (this.client) {
        await this.client.request(
          createItem('task_git_mappings', {
            task_id: taskId,
            branch: branchName,
            created_at: new Date().toISOString()
          })
        );
      }

      console.log(`Created branch ${branchName} for task ${taskId}`);
      return branchName;
    } catch (error: any) {
      throw new Error(`Failed to create branch: ${error.message}`);
    }
  }

  /**
   * Get Git information for task
   */
  async getTaskGitInfo(taskId: string): Promise<any> {
    const mapping = this.taskMappings.get(taskId);
    if (!mapping || !mapping.branch) {
      return null;
    }

    try {
      // Get commits for this task
      const log = await this.git.log({
        '--grep': taskId
      });

      // Get branch info
      const branches = await this.git.branch();
      const branchExists = branches.all.includes(mapping.branch);

      return {
        branch: mapping.branch,
        branchExists,
        commits: log.all,
        commitCount: log.total,
        lastCommit: log.latest
      };
    } catch (error) {
      console.error('Failed to get Git info:', error);
      return null;
    }
  }

  /**
   * Clean up service
   */
  async cleanup(): Promise<void> {
    this.stopWatching();
    this.removeAllListeners();
    this.taskMappings.clear();
  }
}

// Export singleton instance
export const gitEventHandler = new GitEventHandler();