/**
 * Git 同步（plan.md「6.2 Git 同步」）。
 *
 * 通过注入的 GitBackend 实现拉取/合并/推送。
 * - 测试用 MockGitBackend
 * - 生产用 ExpoGitBackend：基于 HTTP（fetch + Basic Auth）的单文件同步。
 *   对单文件 main.bean 而言，等价于"原始内容 PUT + GET"。
 *
 * 冲突解决：main.bean 按行合并（保留双方变更），冲突标记供用户手动解决。
 */

import { logger } from '../../utils/logger';

export interface GitConfig {
  remoteUrl: string;
  branch: string;
  autoSync: boolean;
  syncIntervalMin: number;
}

/** Git 后端抽象（生产用 isomorphic-git）。 */
export interface GitBackend {
  /** 克隆/拉取远程仓库到本地工作目录。 */
  pull(remoteUrl: string, branch: string): Promise<void>;
  /** 提交并推送本地变更。 */
  push(remoteUrl: string, branch: string, message: string): Promise<void>;
  /** 读取文件内容。 */
  readFile(path: string): Promise<string>;
  /** 写入文件内容。 */
  writeFile(path: string, content: string): Promise<void>;
  /** 检查是否有未提交变更。 */
  hasChanges(): Promise<boolean>;
}

export interface GitSyncResult {
  action: 'pulled' | 'pushed' | 'merged' | 'conflict' | 'noop';
  content: string;
  conflicts?: string[];
}

/**
 * 执行 Git 同步（pull → merge → push）。
 */
export async function syncWithGit(
  backend: GitBackend,
  config: GitConfig,
  localContent: string,
): Promise<GitSyncResult> {
  const filePath = 'main.bean';

  // 1. 拉取远程变更
  try {
    await backend.pull(config.remoteUrl, config.branch);
  } catch (e) {
    logger.warn('gitSync', `pull 失败（可能首次同步）: ${e instanceof Error ? e.message : e}`);
  }

  // 2. 读取远程版本
  const remoteContent = await backend.readFile(filePath).catch(() => localContent);

  // 3. 合并
  if (remoteContent === localContent) {
    return { action: 'noop', content: localContent };
  }

  const mergeResult = mergeMobileBean(localContent, remoteContent);
  await backend.writeFile(filePath, mergeResult.content);

  if (mergeResult.conflicts.length > 0) {
    logger.warn('gitSync', `检测到 ${mergeResult.conflicts.length} 处冲突`);
    return {
      action: 'conflict',
      content: mergeResult.content,
      conflicts: mergeResult.conflicts,
    };
  }

  // 4. 提交并推送
  const message = `Sync main.bean from mobile app [${new Date().toISOString()}]`;
  await backend.push(config.remoteUrl, config.branch, message);
  logger.info('gitSync', '同步完成（pushed）');
  return { action: 'pushed', content: mergeResult.content };
}

/**
 * 合并两个 main.bean 版本（按行 union）。
 * 参考 Git 的 3-way merge 简化版：保留双方各自的行。
 */
export function mergeMobileBean(
  local: string,
  remote: string,
): { content: string; conflicts: string[] } {
  if (local === remote) return { content: local, conflicts: [] };

  const localLines = local.split('\n');
  const remoteLines = remote.split('\n');
  const remoteSet = new Set(remoteLines);
  const conflicts: string[] = [];

  // 以 remote 为基础，追加 local 中独有的非空行
  const merged = [...remoteLines];
  for (const line of localLines) {
    if (line.trim() && !remoteSet.has(line)) {
      merged.push(line);
    }
  }

  return {
    content: merged.join('\n'),
    conflicts,  // 简单策略不产生冲突（union）
  };
}

/** 模拟 Git 后端（测试用）。 */
export class MockGitBackend implements GitBackend {
  public files = new Map<string, string>();
  public pulled = false;
  public pushed = false;

  constructor(initialContent = '') {
    this.files.set('main.bean', initialContent);
  }
  async pull(): Promise<void> {
    this.pulled = true;
  }
  async push(): Promise<void> {
    this.pushed = true;
  }
  async readFile(path: string): Promise<string> {
    return this.files.get(path) ?? '';
  }
  async writeFile(path: string, content: string): Promise<void> {
    this.files.set(path, content);
  }
  async hasChanges(): Promise<boolean> {
    return true;
  }
  /** 测试辅助：模拟远程修改。 */
  setRemoteContent(content: string): void {
    this.files.set('main.bean', content);
  }
}

/**
 * 生产 Git 后端：基于 HTTP 的单文件同步。
 *
 * 对单文件 main.bean 而言，不需要完整 git 仓库——通过 HTTP GET 拉取、
 * HTTP PUT 推送即可实现多端同步。支持 GitHub Raw / Gitea / 自建 HTTP 文件服务。
 *
 * 凭据通过 HTTP Basic Auth 传递（username + token/password）。
 * URL 格式：完整的 raw 文件 URL，如
 *   https://raw.githubusercontent.com/user/repo/main/main.bean
 * 或自建服务：https://my-server.com/ledger/main.bean
 */
export class ExpoGitBackend implements GitBackend {
  private remoteContent = '';
  private localContent = '';

  constructor(
    private readonly remoteUrl: string,
    private readonly username: string,
    private readonly password: string,
    private readonly fetchImpl: typeof fetch,
  ) {}

  async pull(): Promise<void> {
    const resp = await this.fetchImpl(this.remoteUrl, {
      headers: this.authHeaders(),
    });
    if (resp.status === 404) {
      this.remoteContent = '';
      return;
    }
    if (!resp.ok) {
      throw new Error(`Git pull HTTP ${resp.status}: ${resp.statusText}`);
    }
    this.remoteContent = await resp.text();
  }

  async push(): Promise<void> {
    const resp = await this.fetchImpl(this.remoteUrl, {
      method: 'PUT',
      headers: {
        ...this.authHeaders(),
        'Content-Type': 'text/plain; charset=utf-8',
      },
      body: this.localContent,
    });
    if (!resp.ok) {
      throw new Error(`Git push HTTP ${resp.status}: ${resp.statusText}`);
    }
  }

  async readFile(path: string): Promise<string> {
    if (path !== 'main.bean') throw new Error(`Unsupported file: ${path}`);
    return this.remoteContent;
  }

  async writeFile(path: string, content: string): Promise<void> {
    this.localContent = content;
  }

  async hasChanges(): Promise<boolean> {
    return this.localContent !== this.remoteContent;
  }

  private authHeaders(): Record<string, string> {
    if (!this.username) return {};
    const token = btoa(`${this.username}:${this.password}`);
    return { Authorization: `Basic ${token}` };
  }
}

