import {
  restoreDiff,
  snapshotManifest,
  snapshotSha256,
  type TaskTreeBackupState,
} from "./backup-contracts.ts";
import { TaskTreeGitBackup, type TaskTreeGitBackupConfig } from "./backup-git.ts";
import { TaskTreeStore } from "./store.ts";

export type TaskTreeBackupConfig = TaskTreeGitBackupConfig & {
  enabled: boolean;
  intervalMs: number;
};

export class TaskTreeBackupService {
  private readonly git: TaskTreeGitBackup;

  constructor(
    private readonly store: TaskTreeStore,
    readonly config: TaskTreeBackupConfig,
  ) {
    this.git = new TaskTreeGitBackup(config);
  }

  async request(reason: string): Promise<void> {
    await this.store.requestBackup(this.config.target, reason);
  }

  async create(options: { dryRun?: boolean; reason?: string } = {}) {
    const snapshot = await this.store.exportSnapshot();
    const manifest = snapshotManifest(snapshot, { target: this.config.target });
    if (options.dryRun) return { dryRun: true, changed: false, manifest };
    this.requireEnabled();
    await this.store.beginBackupAttempt(this.config.target);
    try {
      const published = await this.git.publish(snapshot, options.reason ?? "manual backup");
      await this.store.finishBackupSuccess(this.config.target, published.contentSha256, published.gitCommit);
      return { dryRun: false, ...published };
    } catch (error: any) {
      await this.store.finishBackupFailure(
        this.config.target,
        error?.code ?? "backup_failed",
        error?.message ?? String(error),
      );
      throw error;
    }
  }

  async status(): Promise<{ enabled: boolean; configured: boolean; state: TaskTreeBackupState }> {
    return {
      enabled: this.config.enabled,
      configured: Boolean(this.config.repoSshUrl),
      state: await this.store.backupState(this.config.target),
    };
  }

  async verify() {
    this.requireEnabled();
    const current = await this.store.exportSnapshot();
    const stored = this.git.readCurrent();
    if (stored === null) throw codedError("backup_snapshot_missing", "TaskTree backup snapshot does not exist");
    const databaseSha256 = snapshotSha256(current);
    return {
      verified: databaseSha256 === stored.manifest.contentSha256,
      target: this.config.target,
      databaseSha256,
      backupSha256: stored.manifest.contentSha256,
      gitCommit: stored.gitCommit,
      counts: stored.manifest.counts,
    };
  }

  async restore(options: { confirm: boolean; expectedSha256?: string }) {
    this.requireEnabled();
    const stored = this.git.readCurrent();
    if (stored === null) throw codedError("backup_snapshot_missing", "TaskTree backup snapshot does not exist");
    const current = await this.store.exportSnapshot();
    const diff = restoreDiff(current, stored.snapshot);
    if (!options.confirm) {
      return {
        dryRun: true,
        restored: false,
        target: this.config.target,
        contentSha256: stored.manifest.contentSha256,
        gitCommit: stored.gitCommit,
        diff,
      };
    }
    if (!options.expectedSha256 || options.expectedSha256 !== stored.manifest.contentSha256) {
      throw codedError("backup_restore_sha_required", "confirmed restore requires the exact current backup SHA");
    }
    await this.store.restoreSnapshot(stored.snapshot);
    await this.request("post-restore verification backup");
    return {
      dryRun: false,
      restored: true,
      target: this.config.target,
      contentSha256: stored.manifest.contentSha256,
      gitCommit: stored.gitCommit,
      diff,
    };
  }

  private requireEnabled(): void {
    if (!this.config.enabled) throw codedError("backup_disabled", "TaskTree GitHub backup is disabled");
    if (!this.config.repoSshUrl) throw codedError("backup_repo_missing", "TaskTree backup Git repository is not configured");
  }
}

export function taskTreeBackupConfigFromEnv(
  env: Record<string, string | undefined> = process.env,
): TaskTreeBackupConfig {
  return {
    enabled: env.TASKTREE_BACKUP_ENABLED === "true",
    repoSshUrl: env.TASKTREE_BACKUP_REPO_SSH_URL ?? "",
    branch: env.TASKTREE_BACKUP_BRANCH ?? "main",
    basePath: env.TASKTREE_BACKUP_BASE_PATH ?? "tasktree/development",
    worktreePath: env.TASKTREE_BACKUP_WORKTREE ?? ".state/tasktree/backup-repo",
    target: env.TASKTREE_BACKUP_TARGET ?? "development",
    intervalMs: positiveInteger(env.TASKTREE_BACKUP_INTERVAL_MS, 300_000),
    sshCommand: env.TASKTREE_BACKUP_GIT_SSH_COMMAND ?? "",
    authorName: env.TASKTREE_BACKUP_GIT_AUTHOR_NAME ?? "HWLAB TaskTree",
    authorEmail: env.TASKTREE_BACKUP_GIT_AUTHOR_EMAIL ?? "tasktree@hwlab.local",
    commitMessagePrefix: env.TASKTREE_BACKUP_GIT_COMMIT_PREFIX ?? "tasktree:",
  };
}

export function startTaskTreeBackupScheduler(
  service: TaskTreeBackupService,
  onResult: (result: Record<string, unknown>) => void = () => {},
): { stop: () => Promise<void>; runNow: () => Promise<void> } {
  let current: Promise<void> | null = null;
  const runNow = () => {
    if (!service.config.enabled) return Promise.resolve();
    if (current) return current;
    current = (async () => {
      try {
        const result = await service.create({ reason: "automatic worker snapshot" });
        onResult({ ok: true, event: "tasktree_backup_succeeded", ...result });
      } catch (error: any) {
        onResult({
          ok: false,
          event: "tasktree_backup_failed",
          code: error?.code ?? "backup_failed",
          message: error?.message ?? String(error),
        });
      }
    })().finally(() => {
      current = null;
    });
    return current;
  };
  const timer = setInterval(() => void runNow(), service.config.intervalMs);
  timer.unref();
  void runNow();
  return {
    async stop() {
      clearInterval(timer);
      await current;
    },
    runNow,
  };
}

function positiveInteger(value: string | undefined, fallback: number): number {
  const parsed = Number(value);
  return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
}

function codedError(code: string, message: string): Error {
  return Object.assign(new Error(message), { code });
}
