import { Injectable, Logger } from "@nestjs/common";
import { simpleGit, SimpleGit, StatusResult } from "simple-git";
import {
  ConflictCheckResult,
  ConflictInfo,
  ConflictCheckOptions,
  ConflictDetectionError,
} from "./types";

/**
 * Service for detecting merge conflicts before pushing
 */
@Injectable()
export class ConflictDetectionService {
  private readonly logger = new Logger(ConflictDetectionService.name);

  /**
   * Get a simple-git instance for a local path
   */
  private getGit(localPath: string): SimpleGit {
    return simpleGit(localPath);
  }

  /**
   * Check for conflicts before pushing
   * Fetches latest from remote and attempts a test merge/rebase
   */
  async checkForConflicts(
    localPath: string,
    options?: ConflictCheckOptions
  ): Promise<ConflictCheckResult> {
    const remote = options?.remote ?? "origin";
    const remoteBranch = options?.remoteBranch ?? "develop";
    const strategy = options?.strategy ?? "merge";

    try {
      this.logger.log(
        `Checking for conflicts in ${localPath} with ${remote}/${remoteBranch} using ${strategy}`
      );

      // Get current branch
      const localBranch = await this.getCurrentBranch(localPath);

      // Fetch latest from remote
      await this.fetchRemote(localPath, remote, remoteBranch);

      // Attempt test merge/rebase
      const hasConflicts = await this.attemptMerge(localPath, remote, remoteBranch, strategy);

      if (!hasConflicts) {
        this.logger.log("No conflicts detected");
        return {
          hasConflicts: false,
          conflicts: [],
          strategy,
          canRetry: false,
          remoteBranch,
          localBranch,
        };
      }

      // Detect conflicts
      const conflicts = await this.detectConflicts(localPath);

      // Cleanup - abort the merge/rebase
      await this.cleanupMerge(localPath, strategy);

      this.logger.log(`Detected ${conflicts.length.toString()} conflicts`);

      return {
        hasConflicts: true,
        conflicts,
        strategy,
        canRetry: true,
        remoteBranch,
        localBranch,
      };
    } catch (error) {
      this.logger.error(`Failed to check for conflicts: ${String(error)}`);
      throw new ConflictDetectionError(
        `Failed to check for conflicts in ${localPath}`,
        "checkForConflicts",
        error as Error
      );
    }
  }

  /**
   * Fetch latest from remote
   */
  async fetchRemote(localPath: string, remote = "origin", branch?: string): Promise<void> {
    try {
      this.logger.log(`Fetching from ${remote}${branch ? `/${branch}` : ""}`);
      const git = this.getGit(localPath);

      // Call fetch with appropriate overload based on branch parameter
      if (branch) {
        await git.fetch(remote, branch);
      } else {
        await git.fetch(remote);
      }

      this.logger.log("Successfully fetched from remote");
    } catch (error) {
      this.logger.error(`Failed to fetch from remote: ${String(error)}`);
      throw new ConflictDetectionError(
        `Failed to fetch from ${remote}`,
        "fetchRemote",
        error as Error
      );
    }
  }

  /**
   * Detect conflicts in current state
   */
  async detectConflicts(localPath: string): Promise<ConflictInfo[]> {
    try {
      const git = this.getGit(localPath);
      const status: StatusResult = await git.status();

      const conflicts: ConflictInfo[] = [];

      // Process conflicted files
      for (const file of status.conflicted) {
        // Find the file in status.files to get more details
        const fileStatus = status.files.find((f) => f.path === file);

        // Determine conflict type
        let type: ConflictInfo["type"] = "content";
        if (fileStatus) {
          if (fileStatus.index === "D" || fileStatus.working_dir === "D") {
            type = "delete";
          } else if (fileStatus.index === "A" && fileStatus.working_dir === "A") {
            type = "add";
          } else if (fileStatus.index === "R" || fileStatus.working_dir === "R") {
            type = "rename";
          }
        }

        conflicts.push({
          file,
          type,
        });
      }

      return conflicts;
    } catch (error) {
      this.logger.error(`Failed to detect conflicts: ${String(error)}`);
      throw new ConflictDetectionError(
        `Failed to detect conflicts in ${localPath}`,
        "detectConflicts",
        error as Error
      );
    }
  }

  /**
   * Get current branch name
   */
  async getCurrentBranch(localPath: string): Promise<string> {
    try {
      const git = this.getGit(localPath);
      const branch = await git.revparse(["--abbrev-ref", "HEAD"]);
      return branch.trim();
    } catch (error) {
      this.logger.error(`Failed to get current branch: ${String(error)}`);
      throw new ConflictDetectionError(
        `Failed to get current branch in ${localPath}`,
        "getCurrentBranch",
        error as Error
      );
    }
  }

  /**
   * Attempt a test merge/rebase to detect conflicts
   * Returns true if conflicts detected, false if clean
   */
  private async attemptMerge(
    localPath: string,
    remote: string,
    remoteBranch: string,
    strategy: "merge" | "rebase"
  ): Promise<boolean> {
    const git = this.getGit(localPath);
    const remoteRef = `${remote}/${remoteBranch}`;

    try {
      if (strategy === "merge") {
        // Attempt test merge with --no-commit and --no-ff
        await git.raw(["merge", "--no-commit", "--no-ff", remoteRef]);
      } else {
        // Attempt test rebase
        await git.raw(["rebase", remoteRef]);
      }

      // If we get here, no conflicts
      return false;
    } catch (error) {
      // Check if error is due to conflicts
      const errorMessage = (error as Error).message || String(error);
      if (errorMessage.includes("CONFLICT") || errorMessage.includes("conflict")) {
        // Conflicts detected
        return true;
      }

      // Other error - rethrow
      throw error;
    }
  }

  /**
   * Cleanup after test merge/rebase
   */
  private async cleanupMerge(localPath: string, strategy: "merge" | "rebase"): Promise<void> {
    try {
      const git = this.getGit(localPath);

      if (strategy === "merge") {
        await git.raw(["merge", "--abort"]);
      } else {
        await git.raw(["rebase", "--abort"]);
      }

      this.logger.log(`Cleaned up ${strategy} operation`);
    } catch (error) {
      // Log warning but don't throw - cleanup is best-effort
      this.logger.warn(`Failed to cleanup ${strategy}: ${String(error)}`);
    }
  }
}
