/**
 * Project Sync Manager
 *
 * Fetches remote changes for project clones and optionally fast-forwards
 * the local defaultBranch. Safe to run with active worktrees — only
 * updates remote-tracking refs and the base branch (never checked out
 * directly by any worktree).
 */

import { simpleGit } from 'simple-git';
import type { ProjectRepository } from '../db/repositories/project-repository.js';
import type { EventBus, ProjectSyncedEvent, ProjectSyncFailedEvent } from '../events/types.js';
import { ensureProjectClone } from './project-clones.js';
import { createModuleLogger } from '../logger/index.js';

const log = createModuleLogger('remote-sync');

// =============================================================================
// Types
// =============================================================================

export interface SyncResult {
  projectId: string;
  projectName: string;
  success: boolean;
  fetched: boolean;
  fastForwarded: boolean;
  error?: string;
}

export interface SyncStatus {
  ahead: number;
  behind: number;
  lastFetchedAt: Date | null;
}

export interface BranchDivergence {
  ahead: number;
  behind: number;
}

// =============================================================================
// ProjectSyncManager
// =============================================================================

export class ProjectSyncManager {
  constructor(
    private projectRepository: ProjectRepository,
    private workspaceRoot: string,
    private eventBus?: EventBus,
  ) {}

  /**
   * Sync a single project: git fetch + ff-only merge of defaultBranch.
   */
  async syncProject(projectId: string): Promise<SyncResult> {
    const project = await this.projectRepository.findById(projectId);
    if (!project) {
      return {
        projectId,
        projectName: 'unknown',
        success: false,
        fetched: false,
        fastForwarded: false,
        error: `Project not found: ${projectId}`,
      };
    }

    const result: SyncResult = {
      projectId: project.id,
      projectName: project.name,
      success: false,
      fetched: false,
      fastForwarded: false,
    };

    try {
      const clonePath = await ensureProjectClone(project, this.workspaceRoot);
      const git = simpleGit(clonePath);

      // 1. git fetch origin
      await git.fetch('origin');
      result.fetched = true;
      log.info({ project: project.name }, 'fetched remote');

      // 2. Fast-forward defaultBranch to origin/<defaultBranch>
      const remoteBranch = `origin/${project.defaultBranch}`;
      try {
        await git.raw(['merge', '--ff-only', remoteBranch, project.defaultBranch]);
        result.fastForwarded = true;
        log.info({ project: project.name, branch: project.defaultBranch }, 'fast-forwarded default branch');
      } catch (ffErr) {
        // ff-only failed — local branch has diverged or doesn't exist yet.
        // This is non-fatal; fetch alone still updated remote-tracking refs.
        log.warn(
          { project: project.name, err: (ffErr as Error).message },
          'fast-forward failed (non-fatal)',
        );
      }

      // 3. Update lastFetchedAt
      await this.projectRepository.update(project.id, { lastFetchedAt: new Date() });

      result.success = true;

      // 4. Emit event
      if (this.eventBus) {
        const event: ProjectSyncedEvent = {
          type: 'project:synced',
          timestamp: new Date(),
          payload: {
            projectId: project.id,
            projectName: project.name,
            fetched: result.fetched,
            fastForwarded: result.fastForwarded,
          },
        };
        this.eventBus.emit(event);
      }
    } catch (err) {
      result.error = (err as Error).message;
      log.error({ project: project.name, err: result.error }, 'sync failed');

      if (this.eventBus) {
        const event: ProjectSyncFailedEvent = {
          type: 'project:sync_failed',
          timestamp: new Date(),
          payload: {
            projectId: project.id,
            projectName: project.name,
            error: result.error,
          },
        };
        this.eventBus.emit(event);
      }
    }

    return result;
  }

  /**
   * Sync all registered projects.
   */
  async syncAllProjects(): Promise<SyncResult[]> {
    const projects = await this.projectRepository.findAll();
    const results: SyncResult[] = [];
    for (const project of projects) {
      results.push(await this.syncProject(project.id));
    }
    return results;
  }

  /**
   * Get sync status for a project: ahead/behind vs origin and lastFetchedAt.
   */
  async getSyncStatus(projectId: string): Promise<SyncStatus> {
    const project = await this.projectRepository.findById(projectId);
    if (!project) {
      return { ahead: 0, behind: 0, lastFetchedAt: null };
    }

    try {
      const clonePath = await ensureProjectClone(project, this.workspaceRoot);
      const git = simpleGit(clonePath);
      const { ahead, behind } = await this.revListCount(
        git,
        `origin/${project.defaultBranch}`,
        project.defaultBranch,
      );
      return { ahead, behind, lastFetchedAt: project.lastFetchedAt };
    } catch {
      return { ahead: 0, behind: 0, lastFetchedAt: project.lastFetchedAt };
    }
  }

  /**
   * Get divergence between defaultBranch and an initiative branch.
   */
  async getInitiativeDivergence(
    projectId: string,
    initiativeBranch: string,
  ): Promise<BranchDivergence> {
    const project = await this.projectRepository.findById(projectId);
    if (!project) {
      return { ahead: 0, behind: 0 };
    }

    try {
      const clonePath = await ensureProjectClone(project, this.workspaceRoot);
      const git = simpleGit(clonePath);
      return this.revListCount(git, project.defaultBranch, initiativeBranch);
    } catch {
      return { ahead: 0, behind: 0 };
    }
  }

  // ===========================================================================
  // Private
  // ===========================================================================

  /**
   * Count commits ahead/behind between two refs using rev-list --left-right --count.
   */
  private async revListCount(
    git: ReturnType<typeof simpleGit>,
    left: string,
    right: string,
  ): Promise<{ ahead: number; behind: number }> {
    try {
      const raw = await git.raw([
        'rev-list',
        '--left-right',
        '--count',
        `${left}...${right}`,
      ]);
      const [leftCount, rightCount] = raw.trim().split(/\s+/).map(Number);
      // left-right: leftCount = commits in left not in right (behind), rightCount = commits in right not in left (ahead)
      return { ahead: rightCount ?? 0, behind: leftCount ?? 0 };
    } catch {
      return { ahead: 0, behind: 0 };
    }
  }
}
