import Foundation

public enum PerformanceDiagnosticArchiveWriter {
    public static func write(
        to destinationURL: URL,
        summaryJSON: Data,
        eventLogURLs: [URL],
        fileManager: FileManager = .default
    ) throws -> URL {
        guard !fileManager.fileExists(atPath: destinationURL.path) else {
            throw ArchiveError.destinationAlreadyExists
        }
        let workingDirectory = fileManager.temporaryDirectory
            .appendingPathComponent("Aligner-Diagnostics-\(UUID().uuidString)", isDirectory: true)
        let contentsDirectory = workingDirectory
            .appendingPathComponent("Aligner-Diagnostics", isDirectory: true)

        try fileManager.createDirectory(at: contentsDirectory, withIntermediateDirectories: true)
        defer { try? fileManager.removeItem(at: workingDirectory) }

        try summaryJSON.write(
            to: contentsDirectory.appendingPathComponent("summary.json"),
            options: [.atomic]
        )
        try readmeText.write(
            to: contentsDirectory.appendingPathComponent("README.txt"),
            atomically: true,
            encoding: .utf8
        )

        var copiedLogCount = 0
        for (index, sourceURL) in eventLogURLs.enumerated() where fileManager.fileExists(atPath: sourceURL.path) {
            let name = index == 0 ? "performance-events.log" : "performance-events-previous.log"
            try sanitizedEventLogData(from: sourceURL).write(
                to: contentsDirectory.appendingPathComponent(name),
                options: [.atomic]
            )
            copiedLogCount += 1
        }

        if copiedLogCount == 0 {
            try "No performance events have been recorded yet. Reproduce the issue, then export again.\n".write(
                to: contentsDirectory.appendingPathComponent("performance-events.log"),
                atomically: true,
                encoding: .utf8
            )
        }

        let temporaryArchiveURL = destinationURL
            .deletingLastPathComponent()
            .appendingPathComponent(".Aligner-Diagnostics-\(UUID().uuidString).zip")
        defer { try? fileManager.removeItem(at: temporaryArchiveURL) }
        try createArchive(
            sourceDirectory: contentsDirectory,
            destinationURL: temporaryArchiveURL
        )
        try fileManager.moveItem(at: temporaryArchiveURL, to: destinationURL)
        return destinationURL
    }

    private static func createArchive(sourceDirectory: URL, destinationURL: URL) throws {
        let process = Process()
        process.executableURL = URL(fileURLWithPath: "/usr/bin/ditto")
        process.arguments = [
            "-c",
            "-k",
            "--sequesterRsrc",
            "--keepParent",
            sourceDirectory.path,
            destinationURL.path
        ]
        try process.run()
        process.waitUntilExit()
        guard process.terminationStatus == 0 else {
            throw ArchiveError.dittoFailed(status: process.terminationStatus)
        }
    }

    private static func sanitizedEventLogData(from sourceURL: URL) throws -> Data {
        let source = try String(contentsOf: sourceURL, encoding: .utf8)
        let sanitized = source
            .split(whereSeparator: \.isNewline)
            .compactMap(sanitizedEventLine)
            .joined(separator: "\n")
        return Data((sanitized.isEmpty ? "No exportable performance events were recorded.\n" : sanitized + "\n").utf8)
    }

    private static func sanitizedEventLine(_ line: Substring) -> String? {
        let source = String(line)
        guard let event = tokenValue(named: "event", in: source) else { return nil }

        let allowedKeys = [
            "ts",
            "event",
            "pid",
            "runID",
            "generation",
            "durationMilliseconds",
            "overlayOpenElapsedMilliseconds",
            "startElapsedMilliseconds",
            "snapshotStartElapsedMilliseconds",
            "lastActivationDurationMilliseconds",
            "appCount",
            "windowCount",
            "displayCount",
            "spaceCount",
            "screenshotEligibleCount",
            "screenshotResolvedCount",
            "screenshotPendingCount",
            "screenshotNotRequestedCount",
            "ranOnMainThread",
            "result",
            "reason"
        ]
        let values = allowedKeys.compactMap { key -> String? in
            let value = key == "event" ? event : tokenValue(named: key, in: source)
            return value.map { "\(key)=\($0)" }
        }
        return values.isEmpty ? nil : values.joined(separator: " ")
    }

    private static func tokenValue(named key: String, in line: String) -> String? {
        let prefix = "\(key)="
        guard let range = line.range(of: prefix) else { return nil }
        let suffix = line[range.upperBound...]
        guard let first = suffix.first, first != "\"" else { return nil }
        let value = suffix.prefix { !$0.isWhitespace }
        return value.isEmpty ? nil : String(value)
    }

    private static let readmeText = """
    Aligner performance diagnostics

    This archive contains local performance information for troubleshooting:
    - summary.json: app version, system, display, permissions, preferences, recent Quick Switch timings, application names, and window counts.
    - performance-events.log: recent performance events with a fixed allowlist of timing and count fields.

    It does not include window titles, screenshots, or typed text.
    """

    private enum ArchiveError: LocalizedError {
        case destinationAlreadyExists
        case dittoFailed(status: Int32)

        var errorDescription: String? {
            switch self {
            case .destinationAlreadyExists:
                return "A diagnostic archive already exists at the selected location."
            case .dittoFailed(let status):
                return "Failed to create the diagnostic archive (ditto exit status \(status))."
            }
        }
    }
}
