import Foundation

/// Clones a remote git repository onto disk and owns the "Nucleic Control" location —
/// `~/.nucleic/control/`, where Nucleic keeps repos it manages itself. A controlled repo
/// lives outside iCloud and outside the user's own folders, which (a) sidesteps the
/// sync-conflict / undownloaded-placeholder corruption that bites iCloud-hosted repos and
/// (b) signals the working copy is Nucleic's to manage, not a tree the user edits by hand.
///
/// Pure path math is `static` + unit-testable; the clone itself is an instance method over
/// `GitRunner`. Registration (saving the `Project`, deduping) lives in `AppStore`.
public struct ProjectCloner: Sendable {
    private let git: GitRunner

    public init(git: GitRunner = GitRunner()) {
        self.git = git
    }

    // MARK: - Control location (pure)

    /// Base directory for Nucleic-controlled repos: `~/.nucleic/control`.
    public static var controlBase: String {
        let home = FileManager.default.homeDirectoryForCurrentUser.path
        return ((home as NSString).appendingPathComponent(".nucleic") as NSString)
            .appendingPathComponent("control")
    }

    /// Whether `path` lives at or under the Nucleic-Control base directory — i.e. this is a
    /// repo Nucleic manages, not a user-chosen location. Compared in canonical
    /// (symlink-resolved) form so `/Users/me` and `/System/Volumes/Data/Users/me` match.
    public static func isControlled(_ path: String) -> Bool {
        let canon = GitWorktreeManager.canonical(path)
        let base = GitWorktreeManager.canonical(controlBase)
        return canon == base || canon.hasPrefix(base + "/")
    }

    /// Derive a repo folder name from a git URL — the last path component minus a trailing
    /// `.git`. Handles `https://…/org/repo.git`, scp-style `git@host:org/repo.git`, and
    /// trailing slashes. Falls back to `repo` when nothing usable remains.
    public static func repoFolderName(fromGitURL url: String) -> String {
        var s = url.trimmingCharacters(in: .whitespacesAndNewlines)
        while s.hasSuffix("/") { s.removeLast() }
        if let cut = s.lastIndex(where: { $0 == "/" || $0 == ":" }) {
            s = String(s[s.index(after: cut)...])
        }
        if s.hasSuffix(".git") { s.removeLast(".git".count) }
        let sanitized = Slug.sanitize(s, fallback: "repo")
        return sanitized.isEmpty ? "repo" : sanitized
    }

    /// A collision-free `parent/<folder>` path: returns `parent/folder` if free, else
    /// `parent/folder-2`, `-3`, … until one doesn't exist. Used so a controlled clone never
    /// clobbers an existing controlled repo of the same name.
    public static func uniqueDestination(parent: String, folder: String) -> String {
        let fm = FileManager.default
        let base = (parent as NSString).appendingPathComponent(folder)
        if !fm.fileExists(atPath: base) { return base }
        var n = 2
        while true {
            let candidate = (parent as NSString).appendingPathComponent("\(folder)-\(n)")
            if !fm.fileExists(atPath: candidate) { return candidate }
            n += 1
        }
    }

    // MARK: - Clone / inspect

    /// Clone `url` into `destination` (which must not already exist). Creates the parent
    /// directory tree first and runs `git clone` from there. Non-interactive: a private
    /// repo without cached credentials fails cleanly (GIT_TERMINAL_PROMPT=0) rather than
    /// hanging on a username prompt.
    public func clone(from url: String, into destination: String) async throws {
        let fm = FileManager.default
        let parent = (destination as NSString).deletingLastPathComponent
        try fm.createDirectory(atPath: parent, withIntermediateDirectories: true)
        // Run with the app's configured Managed Git credential (e.g. a Nucleic-managed key) pinned via
        // `IdentitiesOnly`, so the host's ssh never falls back to — or prompts the keychain for — the
        // user's personal ~/.ssh keys. Applied to **every** remote, not just github.com: Managed Git
        // is the one git identity. GIT_SSH_COMMAND governs only ssh transport, so a local-path clone is
        // unaffected; a no-op when no Managed Git credential is configured.
        let (env, cleanup) = GitHubCredentialProvisioner.provisionHost()
        defer { cleanup() }
        try await git.checked(["clone", url, destination], in: parent, env: env)
    }

    /// The branch a fresh clone checked out (the remote's default branch), e.g. `main` or
    /// `master`. Falls back to `main` if it can't be determined (e.g. a detached HEAD).
    public func detectDefaultBranch(at path: String) async -> GitRef {
        let name = try? await git.checked(["rev-parse", "--abbrev-ref", "HEAD"], in: path)
        guard let name, !name.isEmpty, name != "HEAD" else { return GitRef("main") }
        return GitRef(name)
    }

    /// After cloning one local repo into another (the non-destructive "convert to control"
    /// path), point the clone's `origin` at the *source's* upstream rather than the local
    /// source path that `git clone <localPath>` records — so the managed copy fetches/pushes to
    /// the real remote, matching a clone made directly from the URL. Best-effort: if the source
    /// has no `origin` remote, the clone's `origin` (the local source path) is removed instead,
    /// so it isn't left pointing at a directory the user may later move or delete. The clone
    /// keeps all of the source's commits regardless — `git clone` already copied them.
    public func adoptUpstreamRemote(fromLocalSource source: String, into clone: String) async {
        let upstream = (try? await git.checked(["remote", "get-url", "origin"], in: source))?
            .trimmingCharacters(in: .whitespacesAndNewlines)
        if let upstream, !upstream.isEmpty {
            _ = try? await git.run(["remote", "set-url", "origin", upstream], in: clone)
        } else {
            _ = try? await git.run(["remote", "remove", "origin"], in: clone)
        }
    }
}
