//
//  GitSyncServiceTests.swift
//  oAITests
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen

import Testing
import Foundation
@testable import oAI

@Suite("GitSyncService pure helpers")
struct GitSyncServiceTests {

    private var service: GitSyncService { GitSyncService.shared }

    // MARK: - convertToSSH

    @Test("Already-SSH URLs pass through unchanged")
    func convertToSSHAlreadySSH() {
        #expect(service.convertToSSH("git@github.com:user/repo.git") == "git@github.com:user/repo.git")
    }

    @Test("HTTPS URL converts to SSH form")
    func convertToSSHFromHTTPS() {
        #expect(service.convertToSSH("https://gitlab.pm/rune/oAI-Sync.git") == "git@gitlab.pm:rune/oAI-Sync.git")
    }

    @Test("HTTP URL converts to SSH form")
    func convertToSSHFromHTTP() {
        #expect(service.convertToSSH("http://example.com/user/repo.git") == "git@example.com:user/repo.git")
    }

    @Test("Malformed URL with no path segment is returned unchanged")
    func convertToSSHMalformedNoSlash() {
        #expect(service.convertToSSH("https://onlyhost") == "https://onlyhost")
    }

    @Test("Unknown scheme is returned unchanged")
    func convertToSSHUnknownScheme() {
        #expect(service.convertToSSH("ftp://example.com/repo") == "ftp://example.com/repo")
    }

    // MARK: - injectCredentials

    @Test("Injects username and password into an HTTPS URL")
    func injectCredentialsHTTPS() {
        let result = service.injectCredentials("https://gitlab.pm/rune/repo.git", username: "rune", password: "secret")
        #expect(result == "https://rune:secret@gitlab.pm/rune/repo.git")
    }

    @Test("Non-HTTPS URLs are returned unchanged (credentials not injected)")
    func injectCredentialsNonHTTPS() {
        let result = service.injectCredentials("git@gitlab.pm:rune/repo.git", username: "rune", password: "secret")
        #expect(result == "git@gitlab.pm:rune/repo.git")
    }

    // MARK: - sanitizeFilename

    @Test("Strips filesystem-invalid characters, replacing each with a dash")
    func sanitizeFilenameStripsInvalidChars() {
        #expect(service.sanitizeFilename("a/b\\c:d*e?f\"g<h>i|j") == "a-b-c-d-e-f-g-h-i-j")
    }

    @Test("Leaves an already-valid filename untouched")
    func sanitizeFilenameValidInput() {
        #expect(service.sanitizeFilename("My Chat 2026-01-15") == "My Chat 2026-01-15")
    }

    // MARK: - detectSecretsInText

    @Test("Detects an OpenAI-style API key")
    func detectSecretsOpenAIKey() {
        let text = "here's my key: sk-abcdefghijklmnopqrstuvwxyz123456"
        #expect(service.detectSecretsInText(text).contains("OpenAI Key"))
    }

    @Test("Detects a GitHub personal access token")
    func detectSecretsGitHubToken() {
        let text = "token=ghp_abcdefghijklmnopqrstuvwxyz1234567890"
        #expect(service.detectSecretsInText(text).contains("Access Token"))
    }

    @Test("Plain conversational text has no detected secrets")
    func detectSecretsCleanText() {
        #expect(service.detectSecretsInText("Just a normal conversation about the weather.").isEmpty)
    }

    @Test("Does not flag an OpenAI-shaped string one character short of the length threshold")
    func detectSecretsJustBelowThreshold() {
        // "sk-" + 31 chars = 34 total, but the pattern requires 32+ chars after "sk-"
        let shortKey = "sk-" + String(repeating: "a", count: 31)
        #expect(!service.detectSecretsInText(shortKey).contains("OpenAI Key"))
    }

    // MARK: - extractProvider

    @Test("Recognizes github.com, gitlab.com, and gitea URLs by name")
    func extractProviderRecognizesKnownHosts() {
        #expect(GitSyncService.extractProvider(from: "https://github.com/user/repo.git") == "GitHub")
        #expect(GitSyncService.extractProvider(from: "https://gitlab.com/user/repo.git") == "GitLab")
        #expect(GitSyncService.extractProvider(from: "https://my-gitea-instance.example.com/user/repo.git") == "Gitea")
    }

    @Test("Falls back to a generic label for an unrecognized host")
    func extractProviderFallsBackForUnknownHost() {
        #expect(GitSyncService.extractProvider(from: "https://gitlab.pm/rune/oai-swift.git") == "Git repository")
    }

    // MARK: - orphanedExportFilenames

    private func makeExportMarkdown(id: String, name: String = "Test") -> String {
        ConversationExport(
            id: id,
            name: name,
            createdAt: Date(),
            updatedAt: Date(),
            primaryModel: nil,
            messages: [.init(role: "user", content: "hi", timestamp: Date(), tokens: nil, cost: nil, modelId: nil)]
        ).toMarkdown()
    }

    @Test("A file whose ID still exists locally is not orphaned")
    func keepsFilesForExistingConversations() {
        let id = UUID().uuidString
        let files = [(filename: "chat.md", markdown: makeExportMarkdown(id: id))]
        let orphans = GitSyncService.orphanedExportFilenames(currentIds: [id], files: files)
        #expect(orphans.isEmpty)
    }

    @Test("A file whose ID no longer exists locally is orphaned")
    func flagsFilesForDeletedConversations() {
        let deletedId = UUID().uuidString
        let keptId = UUID().uuidString
        let files = [
            (filename: "deleted.md", markdown: makeExportMarkdown(id: deletedId)),
            (filename: "kept.md", markdown: makeExportMarkdown(id: keptId)),
        ]
        let orphans = GitSyncService.orphanedExportFilenames(currentIds: [keptId], files: files)
        #expect(orphans == ["deleted.md"])
    }

    @Test("Files that fail to parse are left alone rather than deleted")
    func unparsableFilesAreNotFlaggedAsOrphans() {
        let files = [(filename: "not-a-conversation.md", markdown: "not a valid export file")]
        let orphans = GitSyncService.orphanedExportFilenames(currentIds: [], files: files)
        #expect(orphans.isEmpty)
    }

    @Test("Empty file list produces no orphans")
    func emptyFileListProducesNoOrphans() {
        #expect(GitSyncService.orphanedExportFilenames(currentIds: [UUID().uuidString], files: []).isEmpty)
    }
}
