// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2025, Deusty, LLC
// All rights reserved.
//
// Redistribution and use of this software in source and binary forms,
// with or without modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
//
// * Neither the name of Deusty nor the names of its contributors may be used
//   to endorse or promote products derived from this software without specific
//   prior written permission of Deusty, LLC.

import XCTest
@testable import CocoaLumberjackSwift

final class DDLogMessageFormatTests: XCTestCase {
    func testMessageFormatCreationWithNoArgs() {
        let format: DDLogMessageFormat = "Message with no args"
        let expectedFormat: String = "Message with no args"
        XCTAssertFalse(format.storage.requiresArgumentParsing)
        XCTAssertEqual(format.format, expectedFormat)
        XCTAssertTrue(format.args.isEmpty)
        XCTAssertEqual(format.formatted, expectedFormat)
    }

    func testMessageFormatCreationWithString() {
        let str: String = "String"
        let substr: Substring = "Substring"
        let format: DDLogMessageFormat = "This is a string: \(str). And this a substring: \(substr)."
        let expectedFormat: String = "This is a string: %@. And this a substring: %@."
        XCTAssertTrue(format.storage.requiresArgumentParsing)
        XCTAssertEqual(format.format, expectedFormat)
        XCTAssertEqual(format.args.count, 2)
        XCTAssertEqual(format.args.first as? String, str)
        XCTAssertEqual(format.args.last as? String, String(substr))
        XCTAssertEqual(format.formatted, String(format: expectedFormat, arguments: [str, String(substr)]))
    }

    func testMessageFormatCreationWithInts() {
        let int8: Int8 = -7
        let uint8: UInt8 = 7
        let int16: Int16 = -42
        let uint16: UInt16 = 42
        let int32: Int32 = -472345
        let uint32: UInt32 = 472345
        let int64: Int64 = -47234532145
        let uint64: UInt64 = 47234532145
        let int: Int = -2345654
        let uint: UInt = 2345654
        let format: DDLogMessageFormat = "Int8: \(int8); UInt8: \(uint8); Int16: \(int16); UInt16: \(uint16); Int32: \(int32); UInt32: \(uint32); Int64: \(int64); UInt64: \(uint64); Int: \(int); UInt: \(uint)"
        let expectedFormat: String = "Int8: %c; UInt8: %c; Int16: %i; UInt16: %u; Int32: %li; UInt32: %lu; Int64: %lli; UInt64: %llu; Int: %lli; UInt: %llu"
        XCTAssertTrue(format.storage.requiresArgumentParsing)
        XCTAssertEqual(format.format, expectedFormat)
        XCTAssertEqual(format.args.count, 10)
        guard format.args.count >= 10 else { return } // prevent crashes
        XCTAssertEqual(format.args[0] as? Int8, int8)
        XCTAssertEqual(format.args[1] as? UInt8, uint8)
        XCTAssertEqual(format.args[2] as? Int16, int16)
        XCTAssertEqual(format.args[3] as? UInt16, uint16)
        XCTAssertEqual(format.args[4] as? Int32, int32)
        XCTAssertEqual(format.args[5] as? UInt32, uint32)
        XCTAssertEqual(format.args[6] as? Int64, int64)
        XCTAssertEqual(format.args[7] as? UInt64, uint64)
        XCTAssertEqual(format.args[8] as? Int, int)
        XCTAssertEqual(format.args[9] as? UInt, uint)
        XCTAssertEqual(format.formatted, String(format: expectedFormat, arguments: [
            int8, uint8, int16, uint16, int32, uint32, int64, uint64, int, uint,
        ]))
    }

    func testMessageFormatCreationWithFloats() {
        let flt: Float = 42.4344
        let dbl: Double = 42.1345512
        let format: DDLogMessageFormat = "Float: \(flt); Double: \(dbl)"
        let expectedFormat: String = "Float: %f; Double: %lf"
        XCTAssertTrue(format.storage.requiresArgumentParsing)
        XCTAssertEqual(format.format, expectedFormat)
        XCTAssertEqual(format.args.count, 2)
        XCTAssertEqual(format.args.first as? Float, flt)
        XCTAssertEqual(format.args.last as? Double, dbl)
        XCTAssertEqual(format.formatted, String(format: expectedFormat, arguments: [flt, dbl]))
    }

    func testMessageFormatCreationWithReferenceConvertibles() {
        let date = Date()
        let uuid = UUID()
        let indexPath = IndexPath(indexes: [1, 2, 3])
        let format: DDLogMessageFormat = "Date: \(date); UUID: \(uuid); IndexPath: \(indexPath)"
        let expectedFormat: String = "Date: %@; UUID: %@; IndexPath: %@"
        XCTAssertTrue(format.storage.requiresArgumentParsing)
        XCTAssertEqual(format.format, expectedFormat)
        XCTAssertEqual(format.args.count, 3)
        guard format.args.count >= 3 else { return } // prevent crashes
        XCTAssertEqual((format.args[0] as? NSDate).map { $0 as Date }, date)
        XCTAssertEqual((format.args[1] as? NSUUID).map { $0 as UUID }, uuid)
        XCTAssertEqual((format.args[2] as? NSIndexPath).map { $0 as IndexPath }, indexPath)
        XCTAssertEqual(format.formatted, String(format: expectedFormat, arguments: [date as NSDate, uuid as NSUUID, indexPath as NSIndexPath]))
    }

    func testMessageFormatCreationWithNSObjects() {
        final class TestObject: NSObject {}

        let obj = TestObject()
        let format: DDLogMessageFormat = "Object: \(obj)"
        let expectedFormat: String = "Object: %@"
        XCTAssertTrue(format.storage.requiresArgumentParsing)
        XCTAssertEqual(format.format, expectedFormat)
        XCTAssertEqual(format.args.count, 1)
        XCTAssertIdentical(format.args.first as? NSObject, obj)
        XCTAssertEqual(format.formatted, String(format: expectedFormat, arguments: [obj]))
    }

    func testMessageFormatCreationWithOtherTypes() {
        struct TestStruct: Sendable, CustomStringConvertible {
            var description: String { "STRUCT DESCRIPTION" }
        }

        let other = TestStruct()
        let format: DDLogMessageFormat = "Other: \(other)"
        let expectedFormat: String = "Other: %@"
        XCTAssertTrue(format.storage.requiresArgumentParsing)
        XCTAssertEqual(format.format, expectedFormat)
        XCTAssertEqual(format.args.count, 1)
        XCTAssertEqual(format.args.first as? String, String(describing: other))
        XCTAssertEqual(format.formatted, String(format: expectedFormat, arguments: [String(describing: other)]))
    }

    func testMessageFormatWithSpaces() {
        let format: DDLogMessageFormat = " this is a message that starts and ends with a space "
        let expectedFormat: String = " this is a message that starts and ends with a space "
        XCTAssertFalse(format.storage.requiresArgumentParsing)
        XCTAssertEqual(format.format, expectedFormat)
        XCTAssertTrue(format.args.isEmpty)
        XCTAssertEqual(format.formatted, expectedFormat)
    }

    func testMessageFormatWithPercentInLiteral() {
        let formatWithoutArgs: DDLogMessageFormat = "This message contains % some % percent %20 signs but no args"
        let formatWithArgs: DDLogMessageFormat = "This message contains % some % percent %20 signs and \(1) other stuff at \(12.34)"
        let expectedFormatWithoutArgs = "This message contains % some % percent %20 signs but no args"
        let expectedFormatWithArgs = "This message contains %% some %% percent %%20 signs and %lli other stuff at %lf"
        XCTAssertFalse(formatWithoutArgs.storage.requiresArgumentParsing)
        XCTAssertTrue(formatWithArgs.storage.requiresArgumentParsing)
        XCTAssertEqual(formatWithoutArgs.format, expectedFormatWithoutArgs)
        XCTAssertEqual(formatWithArgs.format, expectedFormatWithArgs)
        XCTAssertTrue(formatWithoutArgs.args.isEmpty)
        XCTAssertFalse(formatWithArgs.args.isEmpty)
        XCTAssertEqual(formatWithArgs.args.count, 2)
        XCTAssertEqual(formatWithoutArgs.formatted, expectedFormatWithoutArgs)
        XCTAssertEqual(formatWithArgs.formatted, String(format: expectedFormatWithArgs, arguments: formatWithArgs.args))
    }
}
