import AppKit
@testable import Core
import CoreProtocols
import Crawler
import CrawlerModels
import Foundation
import LoggingModels
@testable import MCPCore
@testable import MCPSupport
@testable import Search
import SearchModels
@testable import SearchToolProvider
import SharedConstants
import Testing
import TestSupport

// MARK: - Test Doubles

private struct NoopMarkdownStrategy: Search.MarkdownToStructuredPageStrategy {
    func convert(markdown: String, url: URL?) -> Shared.Models.StructuredDocumentationPage? {
        nil
    }
}

private struct MissingSampleCatalogProvider: Search.SampleCatalogProvider {
    func fetch() async -> Search.SampleCatalogState {
        .missing(onDiskPath: "")
    }
}

// MARK: - MCP Command Tests

// Tests for the `cupertino serve` command
// Verifies server initialization, resource providers, and tool providers

@Suite("MCP Command Tests", .serialized)
struct MCPCommandTests {
    @Test("MCP server initializes successfully")
    func serverInitialization() {
        print("🧪 Test: MCP server initialization")

        _ = MCP.Core.Server(name: "test-server", version: "1.0.0")

        // Verify server is created (server is a non-optional actor)
        // Simply checking it was instantiated successfully

        print("   ✅ Server initialized!")
    }

    @Test("Register documentation resource provider")
    func registerDocsProvider() async throws {
        print("🧪 Test: Register docs provider")

        let tempDir = FileManager.default.temporaryDirectory
            .appendingPathComponent("cupertino-mcp-test-\(UUID().uuidString)")
        defer { try? FileManager.default.removeItem(at: tempDir) }

        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)

        // Create a test markdown file
        let testFile = tempDir.appendingPathComponent("swift/documentation_swift.md")
        try FileManager.default.createDirectory(
            at: testFile.deletingLastPathComponent(),
            withIntermediateDirectories: true
        )
        try "# Swift\n\nTest content about Swift language.".write(to: testFile, atomically: true, encoding: .utf8)

        // Create metadata.json for the test file
        let pageMetadata = Shared.Models.PageMetadata(
            url: "https://developer.apple.com/documentation/swift",
            framework: "swift",
            filePath: testFile.path,
            contentHash: "test-hash",
            depth: 0
        )
        let metadata = Shared.Models.CrawlMetadata(pages: [pageMetadata.url: pageMetadata])
        let metadataFile = tempDir.appendingPathComponent("metadata.json")
        try metadata.save(to: metadataFile)

        let server = MCP.Core.Server(name: "test-server", version: "1.0.0")
        let config = Shared.Configuration(
            crawler: Shared.Configuration.Crawler(outputDirectory: tempDir),
            changeDetection: Shared.Configuration.ChangeDetection(outputDirectory: tempDir),
            output: Shared.Configuration.Output()
        )
        let provider = MCP.Support.DocsResourceProvider(configuration: config, evolutionDirectory: tempDir, archiveDirectory: tempDir, logger: Logging.NoopRecording())

        await server.registerResourceProvider(provider)

        // List resources
        let listResult = try await provider.listResources(cursor: nil)
        let resources = listResult.resources

        #expect(!resources.isEmpty, "Should have at least one resource")

        // Check if any resource contains "swift" (test data may not be first in list)
        let hasSwiftResource = resources.contains { $0.uri.contains("swift") }
        #expect(hasSwiftResource, "Should have at least one resource with 'swift' in URI")
        if let swiftResource = resources.first(where: { $0.uri.contains("swift") }) {
            print("   ✅ Found resource: \(swiftResource.uri)")
        }

        print("   ✅ Docs provider test passed!")
    }

    @Test("Read documentation resource content")
    func readDocsResource() async throws {
        print("🧪 Test: Read docs resource")

        let tempDir = FileManager.default.temporaryDirectory
            .appendingPathComponent("cupertino-mcp-read-test-\(UUID().uuidString)")
        defer { try? FileManager.default.removeItem(at: tempDir) }

        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)

        // Create test markdown
        let testContent = "# Swift Documentation\n\nThis is test content about the Swift language."
        let testFile = tempDir.appendingPathComponent("swift/documentation_swift.md")
        try FileManager.default.createDirectory(
            at: testFile.deletingLastPathComponent(),
            withIntermediateDirectories: true
        )
        try testContent.write(to: testFile, atomically: true, encoding: .utf8)

        let config = Shared.Configuration(
            crawler: Shared.Configuration.Crawler(outputDirectory: tempDir),
            changeDetection: Shared.Configuration.ChangeDetection(outputDirectory: tempDir),
            output: Shared.Configuration.Output()
        )
        let provider = MCP.Support.DocsResourceProvider(configuration: config, evolutionDirectory: tempDir, archiveDirectory: tempDir, logger: Logging.NoopRecording())

        // Read resource
        let result = try await provider.readResource(uri: "apple-docs://swift/documentation_swift")

        #expect(!result.contents.isEmpty, "Content should not be empty")

        if let firstContent = result.contents.first,
           case let .text(textContent) = firstContent {
            #expect(textContent.text.contains("Swift Documentation"), "Content should contain title")
            #expect(textContent.text.contains("test content"), "Content should contain body")
            print("   ✅ Read \(textContent.text.count) characters")
        }

        print("   ✅ Read resource test passed!")
    }

    // `.serialized` removed: the enclosing
    // `@Suite("MCP Command Tests", .serialized)` already serializes every
    // test in this suite. `.serialized` on a non-parameterized `@Test`
    // has no effect and Swift Testing warns about it.
    @Test("Register search tool provider", .tags(.integration))
    @MainActor
    func registerSearchProvider() async throws {
        _ = NSApplication.shared

        print("🧪 Test: Register search tool provider")

        let tempDir = FileManager.default.temporaryDirectory
            .appendingPathComponent("cupertino-search-tool-test-\(UUID().uuidString)")
        defer { try? FileManager.default.removeItem(at: tempDir) }

        // Create search index with test data
        let searchDbPath = tempDir.appendingPathComponent("search.db")
        let searchIndex = try await Search.Index(dbPath: searchDbPath, logger: Logging.NoopRecording())

        // Index a test document
        try await searchIndex.indexDocument(Search.Index.IndexDocumentParams(
            uri: "https://developer.apple.com/documentation/swift",
            source: "apple-docs",
            framework: "swift",
            title: "Swift Programming Language",
            content: "Swift is a powerful programming language for iOS, macOS, and more.",
            filePath: "/test/swift.md",
            contentHash: "test-hash",
            lastCrawled: Date()
        ))

        let server = MCP.Core.Server(name: "test-server", version: "1.0.0")
        let provider = CompositeToolProvider(searchIndex: searchIndex, sampleDatabase: nil)

        await server.registerToolProvider(provider)

        // List tools
        let result = try await provider.listTools(cursor: nil)
        let tools = result.tools

        #expect(!tools.isEmpty, "Should have search tools")

        if let searchTool = tools.first(where: { $0.name == "search" }) {
            #expect(searchTool.name == "search", "Should have search tool")
            print("   ✅ Found tool: \(searchTool.name)")
        }

        print("   ✅ Search provider test passed!")
    }

    // `.serialized` removed (same reason as the previous test).
    @Test("Execute search tool", .tags(.integration))
    @MainActor
    func executeSearchTool() async throws {
        _ = NSApplication.shared

        print("🧪 Test: Execute search tool")

        let tempDir = FileManager.default.temporaryDirectory
            .appendingPathComponent("cupertino-search-exec-test-\(UUID().uuidString)")
        defer { try? FileManager.default.removeItem(at: tempDir) }

        // Create and populate search index
        let searchDbPath = tempDir.appendingPathComponent("search.db")
        let searchIndex = try await Search.Index(dbPath: searchDbPath, logger: Logging.NoopRecording())

        try await searchIndex.indexDocument(Search.Index.IndexDocumentParams(
            uri: "https://developer.apple.com/documentation/swift/array",
            source: "apple-docs",
            framework: "swift",
            title: "Array",
            content: "An ordered, random-access collection of elements.",
            filePath: "/test/array.md",
            contentHash: "test-hash-array",
            lastCrawled: Date()
        ))

        let provider = CompositeToolProvider(searchIndex: searchIndex, sampleDatabase: nil)

        // Execute search
        let arguments: [String: MCP.Core.Protocols.AnyCodable] = [
            "query": MCP.Core.Protocols.AnyCodable("array"),
            "limit": MCP.Core.Protocols.AnyCodable(5),
        ]

        let result = try await provider.callTool(name: "search", arguments: arguments)

        #expect(!result.content.isEmpty, "Search should return results")

        if let firstResult = result.content.first,
           case let .text(textContent) = firstResult {
            #expect(textContent.text.contains("Array"), "Result should contain 'Array'")
            print("   ✅ Search returned: \(textContent.text.prefix(100))...")
        }

        print("   ✅ Search execution test passed!")
    }

    @Test("Swift Evolution resource provider")
    func evolutionResourceProvider() async throws {
        print("🧪 Test: Swift Evolution provider")

        let tempDir = FileManager.default.temporaryDirectory
            .appendingPathComponent("cupertino-evolution-provider-test-\(UUID().uuidString)")
        defer { try? FileManager.default.removeItem(at: tempDir) }

        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)

        // Create test proposal
        let testProposal = "# SE-0255: Implicit returns from single-expression functions\n\nTest content."
        let testFile = tempDir.appendingPathComponent("SE-0255-omit-return.md")
        try testProposal.write(to: testFile, atomically: true, encoding: .utf8)

        let config = Shared.Configuration(
            crawler: Shared.Configuration.Crawler(outputDirectory: tempDir),
            changeDetection: Shared.Configuration.ChangeDetection(outputDirectory: tempDir),
            output: Shared.Configuration.Output()
        )
        let provider = MCP.Support.DocsResourceProvider(configuration: config, evolutionDirectory: tempDir, archiveDirectory: tempDir, logger: Logging.NoopRecording())

        // List resources
        let listResult = try await provider.listResources(cursor: nil as String?)
        let resources = listResult.resources

        #expect(!resources.isEmpty, "Should have evolution proposals")

        // Check if any resource contains "SE-" (test data may not be first in list)
        let hasProposal = resources.contains { $0.uri.contains("SE-") }
        #expect(hasProposal, "Should have at least one resource with 'SE-' in URI")
        if let proposal = resources.first(where: { $0.uri.contains("SE-") }) {
            print("   ✅ Found proposal: \(proposal.uri)")
        }

        // Read resource
        let readResult = try await provider.readResource(uri: "swift-evolution://SE-0255")

        if let firstContent = readResult.contents.first,
           case let .text(textContent) = firstContent {
            #expect(textContent.text.contains("SE-0255"), "Content should contain proposal number")
            print("   ✅ Read proposal content")
        }

        print("   ✅ Evolution provider test passed!")
    }

    @Test("Swift Testing (ST) resource provider lists and reads ST proposals")
    func stResourceProvider() async throws {
        print("🧪 Test: Swift Testing (ST) provider")

        let tempDir = FileManager.default.temporaryDirectory
            .appendingPathComponent("cupertino-st-provider-test-\(UUID().uuidString)")
        defer { try? FileManager.default.removeItem(at: tempDir) }

        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)

        // Create test SE proposal
        let seProposal = "# SE-0255: Implicit returns\n\n* Status: **Implemented (Swift 5.1)**\n\nTest content."
        let seFile = tempDir.appendingPathComponent("SE-0255-omit-return.md")
        try seProposal.write(to: seFile, atomically: true, encoding: .utf8)

        // Create test ST proposal
        let stProposal = "# Refactor Bug Inits\n\nSwift Testing proposal about refactoring bug initializers."
        let stFile = tempDir.appendingPathComponent("ST-0001-refactor-bug-inits.md")
        try stProposal.write(to: stFile, atomically: true, encoding: .utf8)

        let config = Shared.Configuration(
            crawler: Shared.Configuration.Crawler(outputDirectory: tempDir),
            changeDetection: Shared.Configuration.ChangeDetection(outputDirectory: tempDir),
            output: Shared.Configuration.Output()
        )
        let provider = MCP.Support.DocsResourceProvider(configuration: config, evolutionDirectory: tempDir, archiveDirectory: tempDir, logger: Logging.NoopRecording())

        // List resources — should include both SE and ST
        let listResult = try await provider.listResources(cursor: nil as String?)
        let resources = listResult.resources

        let hasSEProposal = resources.contains { $0.uri.contains("SE-") }
        let hasSTProposal = resources.contains { $0.uri.contains("ST-") }
        #expect(hasSEProposal, "Should list SE proposals")
        #expect(hasSTProposal, "Should list ST proposals")

        // Read ST resource
        let readResult = try await provider.readResource(uri: "swift-evolution://ST-0001")

        if let firstContent = readResult.contents.first,
           case let .text(textContent) = firstContent {
            #expect(textContent.text.contains("Refactor Bug Inits"), "Content should contain ST proposal title")
            print("   ✅ Read ST proposal content")
        }

        print("   ✅ ST provider test passed!")
    }

    @Test("MCP server handles invalid requests gracefully")
    func serverErrorHandling() async throws {
        print("🧪 Test: Server error handling")

        let tempDir = FileManager.default.temporaryDirectory
            .appendingPathComponent("cupertino-error-test-\(UUID().uuidString)")
        defer { try? FileManager.default.removeItem(at: tempDir) }

        let config = Shared.Configuration(
            crawler: Shared.Configuration.Crawler(outputDirectory: tempDir),
            changeDetection: Shared.Configuration.ChangeDetection(outputDirectory: tempDir),
            output: Shared.Configuration.Output()
        )
        let provider = MCP.Support.DocsResourceProvider(configuration: config, evolutionDirectory: tempDir, archiveDirectory: tempDir, logger: Logging.NoopRecording())

        // Try to read non-existent resource
        await #expect(throws: Shared.Core.ToolError.self) {
            _ = try await provider.readResource(uri: "apple-docs://nonexistent/file")
        }

        print("   ✅ Error handling test passed!")
    }
}

// MARK: - Integration Test: Full MCP Flow

@Suite("MCP Server Integration", .serialized)
struct MCPServerIntegrationTests {
    @Test("Complete MCP workflow", .tags(.integration, .slow))
    @MainActor
    func completeMCPWorkflow() async throws {
        _ = NSApplication.shared

        print("🧪 Integration Test: Complete MCP workflow")
        print("   This test simulates the full MCP server usage:")
        print("   1. Crawl docs")
        print("   2. Build index")
        print("   3. Start MCP server")
        print("   4. Search via tool")
        print("   5. Read via resource")

        let tempDir = FileManager.default.temporaryDirectory
            .appendingPathComponent("cupertino-full-mcp-test-\(UUID().uuidString)")
        defer { try? FileManager.default.removeItem(at: tempDir) }

        // Step 1: Crawl
        print("\n   📥 Step 1: Crawling documentation...")
        let config = try Shared.Configuration(
            crawler: Shared.Configuration.Crawler(
                startURL: #require(URL(string: "https://developer.apple.com/documentation/swift")),
                maxPages: 1,
                maxDepth: 0,
                outputDirectory: tempDir
            ),
            changeDetection: Shared.Configuration.ChangeDetection(forceRecrawl: true, outputDirectory: tempDir),
            output: Shared.Configuration.Output(format: .markdown)
        )

        let crawler = await Crawler.AppleDocs(
            configuration: config,
            htmlParser: Crawler.NoopHTMLParserStrategy(),
            appleJSONParser: Crawler.NoopAppleJSONParserStrategy(),
            priorityPackageStrategy: Crawler.NoopPriorityPackageStrategy(),
            logger: Logging.NoopRecording()
        )
        let stats = try await crawler.crawl()
        #expect(stats.totalPages > 0, "Should have crawled pages")
        print("   ✅ Crawled \(stats.totalPages) page(s)")

        // Step 2: Build index
        print("\n   🔍 Step 2: Building search index...")
        let searchDbPath = tempDir.appendingPathComponent("search.db")
        let searchIndex = try await Search.Index(dbPath: searchDbPath, logger: Logging.NoopRecording())

        let metadata = try Shared.Models.CrawlMetadata.load(from: tempDir.appendingPathComponent("metadata.json"))
        let builder = Search.IndexBuilder(
            searchIndex: searchIndex,
            metadata: metadata,
            docsDirectory: tempDir,
            evolutionDirectory: nil,
            markdownStrategy: NoopMarkdownStrategy(),
            sampleCatalogProvider: MissingSampleCatalogProvider(), logger: Logging.NoopRecording()
        )
        try await builder.buildIndex()
        print("   ✅ Index built")

        // Step 3: Initialize MCP server
        print("\n   🚀 Step 3: Starting MCP server...")
        let server = MCP.Core.Server(name: "test-server", version: "1.0.0")

        // Register providers. evolutionDirectory + archiveDirectory point at
        // sibling dirs that don't exist so listResources only emits the
        // apple-docs entries from the metadata. Pre-#535 the test relied on
        // archiveDirectory silently defaulting to ~/.cupertino/archive via
        // BinaryConfig.shared and sorting a real archive resource first;
        // strict DI removes that, so we also pre-seed a markdown file at
        // the path the apple-docs URI fallback expects so Step 5's
        // readResource(uri:) succeeds without depending on the host's
        // production ~/.cupertino layout.
        let mcpConfig = Shared.Configuration(
            crawler: Shared.Configuration.Crawler(outputDirectory: tempDir),
            changeDetection: Shared.Configuration.ChangeDetection(outputDirectory: tempDir),
            output: Shared.Configuration.Output()
        )
        let seededDocsPath = tempDir
            .appendingPathComponent("swift")
            .appendingPathComponent("documentation_swift.md")
        try FileManager.default.createDirectory(
            at: seededDocsPath.deletingLastPathComponent(),
            withIntermediateDirectories: true
        )
        try "# Swift\n\nSeeded by completeMCPWorkflow.".write(
            to: seededDocsPath,
            atomically: true,
            encoding: .utf8
        )
        let docsProvider = MCP.Support.DocsResourceProvider(
            configuration: mcpConfig,
            evolutionDirectory: tempDir.appendingPathComponent("nonexistent-evolution"),
            archiveDirectory: tempDir.appendingPathComponent("nonexistent-archive"),
            logger: Logging.NoopRecording()
        )
        let searchProvider = CompositeToolProvider(searchIndex: searchIndex, sampleDatabase: nil)

        await server.registerResourceProvider(docsProvider)
        await server.registerToolProvider(searchProvider)
        print("   ✅ Server initialized with providers")

        // Step 4: Search via tool
        print("\n   🔎 Step 4: Searching via MCP tool...")
        let searchArgs: [String: MCP.Core.Protocols.AnyCodable] = [
            "query": MCP.Core.Protocols.AnyCodable("swift"),
            "limit": MCP.Core.Protocols.AnyCodable(5),
        ]
        let searchResults = try await searchProvider.callTool(name: "search", arguments: searchArgs)
        #expect(!searchResults.content.isEmpty, "Search should return results")
        print("   ✅ Search returned results")

        // Step 5: Read via resource
        print("\n   📖 Step 5: Reading via MCP resource...")
        let listResourcesResult = try await docsProvider.listResources(cursor: nil as String?)
        let resources = listResourcesResult.resources
        #expect(!resources.isEmpty, "Should have resources")

        if let firstResource = resources.first {
            let readResult = try await docsProvider.readResource(uri: firstResource.uri)
            #expect(!readResult.contents.isEmpty, "Resource content should not be empty")
            print("   ✅ Read resource: \(firstResource.name)")
        }

        print("\n   🎉 Complete MCP workflow test passed!")
    }
}

// Note: Test tags are now defined in TestSupport/TestTags.swift
