import { exec } from 'child_process';
import { promisify } from 'util';
import {
  GitBranch,
  GitCommit,
  GitTag,
  GitRemote,
  PullRequest,
  GitWorkflow,
  WorkflowStep,
  GitStats,
  Contributor,
  GitConfig,
  CommitConvention,
  GitHook,
} from './types.js';

const execAsync = promisify(exec);

export class GitManager {
  private config: GitConfig;

  constructor(config?: GitConfig) {
    this.config = config || this.loadGitConfig();
  }

  async getBranches(): Promise<GitBranch[]> {
    const { stdout } = await execAsync('git branch -a -v');
    const lines = stdout.trim().split('\n');
    const branches: GitBranch[] = [];

    for (const line of lines) {
      const current = line.startsWith('*');
      const parts = line.substring(2).trim().split(/\s+/);
      const name = parts[0];
      const hash = parts[1];
      
      if (name === '->') continue; // Skip symbolic refs

      const branch: GitBranch = {
        name: name.replace('remotes/', ''),
        current,
        ahead: 0,
        behind: 0,
      };

      // Get ahead/behind info for local branches
      if (!name.startsWith('remotes/')) {
        try {
          const { stdout: counts } = await execAsync(
            `git rev-list --left-right --count ${name}...origin/${name} 2>/dev/null`
          );
          const [ahead, behind] = counts.trim().split('\t').map(Number);
          branch.ahead = ahead || 0;
          branch.behind = behind || 0;
        } catch {
          // Branch might not have upstream
        }
      }

      branches.push(branch);
    }

    return branches;
  }

  async createBranch(name: string, from?: string): Promise<GitBranch> {
    const source = from || 'HEAD';
    await execAsync(`git checkout -b ${name} ${source}`);
    
    return {
      name,
      current: true,
      ahead: 0,
      behind: 0,
    };
  }

  async switchBranch(name: string): Promise<void> {
    await execAsync(`git checkout ${name}`);
  }

  async deleteBranch(name: string, force: boolean = false): Promise<void> {
    const flag = force ? '-D' : '-d';
    await execAsync(`git branch ${flag} ${name}`);
  }

  async getCommits(options: {
    branch?: string;
    limit?: number;
    since?: string;
    until?: string;
    author?: string;
  } = {}): Promise<GitCommit[]> {
    let command = 'git log --pretty=format:"%H|%h|%an|%ae|%ad|%s" --date=iso';
    
    if (options.branch) command += ` ${options.branch}`;
    if (options.limit) command += ` -n ${options.limit}`;
    if (options.since) command += ` --since="${options.since}"`;
    if (options.until) command += ` --until="${options.until}"`;
    if (options.author) command += ` --author="${options.author}"`;

    const { stdout } = await execAsync(command);
    const lines = stdout.trim().split('\n').filter(Boolean);
    
    return lines.map(line => {
      const [hash, shortHash, author, email, date, message] = line.split('|');
      return {
        hash,
        shortHash,
        author,
        email,
        date,
        message,
      };
    });
  }

  async getCommitDetails(hash: string): Promise<GitCommit> {
    const { stdout: info } = await execAsync(
      `git show --pretty=format:"%H|%h|%an|%ae|%ad|%s" --date=iso ${hash} | head -1`
    );
    
    const [fullHash, shortHash, author, email, date, message] = info.split('|');
    
    // Get file changes
    const { stdout: files } = await execAsync(
      `git show --pretty="" --name-status ${hash}`
    );
    
    const fileChanges = files.trim().split('\n').filter(Boolean).map(line => {
      const [status, ...pathParts] = line.split('\t');
      const path = pathParts.join('\t');
      
      return {
        path,
        status: status.toLowerCase() as any,
        additions: 0,
        deletions: 0,
      };
    });

    // Get stats
    const { stdout: stats } = await execAsync(
      `git show --stat --format="" ${hash} | tail -1`
    );
    
    const statsMatch = stats.match(/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/);
    
    return {
      hash: fullHash,
      shortHash,
      author,
      email,
      date,
      message,
      files: fileChanges,
      stats: statsMatch ? {
        files: parseInt(statsMatch[1]) || 0,
        additions: parseInt(statsMatch[2]) || 0,
        deletions: parseInt(statsMatch[3]) || 0,
      } : undefined,
    };
  }

  async createCommit(message: string, options: {
    all?: boolean;
    amend?: boolean;
    noVerify?: boolean;
  } = {}): Promise<GitCommit> {
    let command = 'git commit';
    
    if (options.all) command += ' -a';
    if (options.amend) command += ' --amend';
    if (options.noVerify) command += ' --no-verify';
    
    command += ` -m "${message}"`;
    
    await execAsync(command);
    
    // Get the created commit
    const commits = await this.getCommits({ limit: 1 });
    return commits[0];
  }

  async cherryPick(commits: string[], options: {
    noCommit?: boolean;
    mainline?: number;
  } = {}): Promise<void> {
    let command = 'git cherry-pick';
    
    if (options.noCommit) command += ' -n';
    if (options.mainline) command += ` -m ${options.mainline}`;
    
    command += ' ' + commits.join(' ');
    
    await execAsync(command);
  }

  async rebase(onto: string, options: {
    interactive?: boolean;
    autosquash?: boolean;
    preserveMerges?: boolean;
  } = {}): Promise<void> {
    let command = `git rebase ${onto}`;
    
    if (options.interactive) command += ' -i';
    if (options.autosquash) command += ' --autosquash';
    if (options.preserveMerges) command += ' -p';
    
    await execAsync(command);
  }

  async getTags(): Promise<GitTag[]> {
    const { stdout } = await execAsync('git tag -l --format="%(refname:short)|%(objectname)|%(taggerdate:iso)|%(subject)"');
    const lines = stdout.trim().split('\n').filter(Boolean);
    
    return lines.map(line => {
      const [name, commit, date, message] = line.split('|');
      return {
        name,
        commit,
        date,
        message,
      };
    });
  }

  async createTag(name: string, options: {
    message?: string;
    commit?: string;
    force?: boolean;
  } = {}): Promise<GitTag> {
    let command = `git tag ${name}`;
    
    if (options.message) command = `git tag -a ${name} -m "${options.message}"`;
    if (options.commit) command += ` ${options.commit}`;
    if (options.force) command += ' -f';
    
    await execAsync(command);
    
    return {
      name,
      commit: options.commit || 'HEAD',
      message: options.message,
      date: new Date().toISOString(),
    };
  }

  async getRemotes(): Promise<GitRemote[]> {
    const { stdout } = await execAsync('git remote -v');
    const lines = stdout.trim().split('\n').filter(Boolean);
    const remotes: Map<string, GitRemote> = new Map();
    
    for (const line of lines) {
      const [name, url, type] = line.split(/\s+/);
      const existing = remotes.get(name);
      
      if (existing) {
        existing.type = 'both';
      } else {
        remotes.set(name, {
          name,
          url,
          type: type.includes('fetch') ? 'fetch' : 'push',
        });
      }
    }
    
    return Array.from(remotes.values());
  }

  async push(options: {
    remote?: string;
    branch?: string;
    force?: boolean;
    tags?: boolean;
    setUpstream?: boolean;
  } = {}): Promise<void> {
    let command = 'git push';
    
    if (options.force) command += ' --force-with-lease';
    if (options.tags) command += ' --tags';
    if (options.setUpstream) command += ' -u';
    
    const remote = options.remote || 'origin';
    const branch = options.branch || 'HEAD';
    
    command += ` ${remote} ${branch}`;
    
    await execAsync(command);
  }

  async pull(options: {
    remote?: string;
    branch?: string;
    rebase?: boolean;
    noCommit?: boolean;
  } = {}): Promise<void> {
    let command = 'git pull';
    
    if (options.rebase) command += ' --rebase';
    if (options.noCommit) command += ' --no-commit';
    
    if (options.remote) command += ` ${options.remote}`;
    if (options.branch) command += ` ${options.branch}`;
    
    await execAsync(command);
  }

  async stash(options: {
    message?: string;
    includeUntracked?: boolean;
    keepIndex?: boolean;
  } = {}): Promise<void> {
    let command = 'git stash push';
    
    if (options.message) command += ` -m "${options.message}"`;
    if (options.includeUntracked) command += ' -u';
    if (options.keepIndex) command += ' -k';
    
    await execAsync(command);
  }

  async stashPop(index?: number): Promise<void> {
    const command = index !== undefined ? `git stash pop stash@{${index}}` : 'git stash pop';
    await execAsync(command);
  }

  async getStatus(): Promise<{
    branch: string;
    upstream?: string;
    ahead: number;
    behind: number;
    staged: string[];
    unstaged: string[];
    untracked: string[];
  }> {
    const { stdout } = await execAsync('git status --porcelain=v2 --branch');
    const lines = stdout.trim().split('\n');
    
    const status = {
      branch: '',
      upstream: undefined as string | undefined,
      ahead: 0,
      behind: 0,
      staged: [] as string[],
      unstaged: [] as string[],
      untracked: [] as string[],
    };
    
    for (const line of lines) {
      if (line.startsWith('# branch.head')) {
        status.branch = line.split(' ')[2];
      } else if (line.startsWith('# branch.upstream')) {
        status.upstream = line.split(' ')[2];
      } else if (line.startsWith('# branch.ab')) {
        const parts = line.split(' ');
        status.ahead = parseInt(parts[2].substring(1));
        status.behind = Math.abs(parseInt(parts[3].substring(1)));
      } else if (line.startsWith('1') || line.startsWith('2')) {
        // File changes
        const parts = line.split(' ');
        const file = parts[parts.length - 1];
        const xy = parts[1];
        
        if (xy[0] !== '.') status.staged.push(file);
        if (xy[1] !== '.') status.unstaged.push(file);
      } else if (line.startsWith('?')) {
        status.untracked.push(line.substring(2));
      }
    }
    
    return status;
  }

  async startWorkflow(type: GitWorkflow['type'], name: string): Promise<GitWorkflow> {
    const workflows: Record<GitWorkflow['type'], Partial<GitWorkflow>> = {
      feature: {
        sourceBranch: this.config.branch?.default || 'main',
        targetBranch: this.config.branch?.default || 'main',
        steps: [
          {
            name: 'Create feature branch',
            description: 'Create a new branch for the feature',
            command: `git checkout -b ${this.config.workflow?.featurePrefix || 'feature/'}${name}`,
            completed: false,
          },
          {
            name: 'Implement feature',
            description: 'Make changes and commit them',
            completed: false,
          },
          {
            name: 'Push branch',
            description: 'Push the feature branch to remote',
            command: 'git push -u origin HEAD',
            completed: false,
          },
          {
            name: 'Create pull request',
            description: 'Open a pull request for review',
            completed: false,
          },
          {
            name: 'Merge to main',
            description: 'Merge the feature after approval',
            completed: false,
          },
        ],
      },
      release: {
        sourceBranch: this.config.branch?.default || 'main',
        targetBranch: this.config.branch?.default || 'main',
        steps: [
          {
            name: 'Create release branch',
            description: 'Branch off from develop/main',
            command: `git checkout -b ${this.config.workflow?.releasePrefix || 'release/'}${name}`,
            completed: false,
          },
          {
            name: 'Bump version',
            description: 'Update version numbers',
            completed: false,
          },
          {
            name: 'Test release',
            description: 'Run final tests',
            completed: false,
          },
          {
            name: 'Tag release',
            description: 'Create version tag',
            command: `git tag -a v${name} -m "Release version ${name}"`,
            completed: false,
          },
          {
            name: 'Merge to main',
            description: 'Merge release to main branch',
            completed: false,
          },
          {
            name: 'Back-merge to develop',
            description: 'Merge changes back to develop',
            completed: false,
          },
        ],
      },
      hotfix: {
        sourceBranch: this.config.branch?.default || 'main',
        targetBranch: this.config.branch?.default || 'main',
        steps: [
          {
            name: 'Create hotfix branch',
            description: 'Branch off from main',
            command: `git checkout -b ${this.config.workflow?.hotfixPrefix || 'hotfix/'}${name} main`,
            completed: false,
          },
          {
            name: 'Fix issue',
            description: 'Implement the hotfix',
            completed: false,
          },
          {
            name: 'Test fix',
            description: 'Verify the fix works',
            completed: false,
          },
          {
            name: 'Merge to main',
            description: 'Apply hotfix to production',
            completed: false,
          },
          {
            name: 'Back-merge',
            description: 'Merge fix back to develop',
            completed: false,
          },
        ],
      },
      custom: {
        sourceBranch: 'HEAD',
        targetBranch: this.config.branch?.default || 'main',
        steps: [],
      },
    };

    const workflow: GitWorkflow = {
      name,
      type,
      sourceBranch: workflows[type].sourceBranch!,
      targetBranch: workflows[type].targetBranch!,
      steps: workflows[type].steps || [],
      currentStep: 0,
      status: 'active',
    };

    // Execute first step if it has a command
    if (workflow.steps[0]?.command) {
      await execAsync(workflow.steps[0].command);
      workflow.steps[0].completed = true;
      workflow.currentStep = 1;
    }

    return workflow;
  }

  async getStats(): Promise<GitStats> {
    // Get total commits
    const { stdout: commitCount } = await execAsync('git rev-list --all --count');
    const totalCommits = parseInt(commitCount.trim());

    // Get contributors
    const { stdout: shortlog } = await execAsync('git shortlog -sne --all');
    const contributors: Contributor[] = shortlog.trim().split('\n').map(line => {
      const match = line.match(/^\s*(\d+)\s+(.+?)\s+<(.+?)>$/);
      if (!match) return null;
      
      const [, commits, name, email] = match;
      return {
        name,
        email,
        commits: parseInt(commits),
        additions: 0,
        deletions: 0,
        firstCommit: '',
        lastCommit: '',
      };
    }).filter(Boolean) as Contributor[];

    // Get branch count
    const { stdout: branchList } = await execAsync('git branch -a | wc -l');
    const branches = parseInt(branchList.trim());

    // Get tag count
    const { stdout: tagList } = await execAsync('git tag | wc -l');
    const tags = parseInt(tagList.trim());

    // Get activity stats (simplified)
    const activity = {
      daily: {},
      weekly: {},
      monthly: {},
    };

    return {
      totalCommits,
      contributors,
      branches,
      tags,
      activity,
    };
  }

  async createPullRequest(options: {
    title: string;
    description: string;
    sourceBranch: string;
    targetBranch?: string;
    labels?: string[];
    reviewers?: string[];
  }): Promise<PullRequest> {
    // This would integrate with GitHub/GitLab/Bitbucket API
    // For now, we'll use GitHub CLI if available
    try {
      const target = options.targetBranch || this.config.branch?.default || 'main';
      let command = `gh pr create --title "${options.title}" --body "${options.description}" --base ${target}`;
      
      if (options.labels?.length) {
        command += ` --label ${options.labels.join(',')}`;
      }
      
      if (options.reviewers?.length) {
        command += ` --reviewer ${options.reviewers.join(',')}`;
      }

      const { stdout } = await execAsync(command);
      const prUrl = stdout.trim();
      const prNumber = parseInt(prUrl.split('/').pop() || '0');

      return {
        number: prNumber,
        title: options.title,
        description: options.description,
        sourceBranch: options.sourceBranch,
        targetBranch: target,
        author: this.config.user?.name || 'unknown',
        created: new Date().toISOString(),
        status: 'open',
        labels: options.labels,
      };
    } catch (error) {
      throw new Error('Failed to create pull request. Make sure GitHub CLI is installed and authenticated.');
    }
  }

  async setupGitHooks(hooks: GitHook[]): Promise<void> {
    const hooksDir = '.git/hooks';
    
    for (const hook of hooks) {
      if (!hook.enabled) continue;
      
      const hookPath = `${hooksDir}/${hook.name}`;
      const { writeFile, chmod } = await import('fs/promises');
      
      await writeFile(hookPath, hook.script, 'utf-8');
      await chmod(hookPath, 0o755);
    }
  }

  async validateCommitMessage(message: string, convention: CommitConvention): Promise<{
    valid: boolean;
    errors: string[];
  }> {
    const errors: string[] = [];
    
    if (convention.type === 'conventional') {
      // Conventional Commits format: type(scope): description
      const pattern = /^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{1,50}/;
      
      if (!pattern.test(message)) {
        errors.push('Commit message must follow Conventional Commits format');
        errors.push('Example: feat(auth): add login functionality');
      }
      
      // Check if type is allowed
      if (convention.types?.length) {
        const type = message.split(/[(:]/)[0];
        if (!convention.types.includes(type)) {
          errors.push(`Commit type must be one of: ${convention.types.join(', ')}`);
        }
      }
    }
    
    return {
      valid: errors.length === 0,
      errors,
    };
  }

  private loadGitConfig(): GitConfig {
    // Would load from git config
    return {
      user: {},
      remote: {},
      branch: {
        default: 'main',
      },
      workflow: {
        featurePrefix: 'feature/',
        releasePrefix: 'release/',
        hotfixPrefix: 'hotfix/',
      },
    };
  }

  async getConflicts(): Promise<string[]> {
    try {
      const { stdout } = await execAsync('git diff --name-only --diff-filter=U');
      return stdout.trim().split('\n').filter(Boolean);
    } catch {
      return [];
    }
  }

  async resolveConflict(file: string, resolution: 'ours' | 'theirs'): Promise<void> {
    await execAsync(`git checkout --${resolution} ${file}`);
    await execAsync(`git add ${file}`);
  }
}