// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
//
// stringlint:disable

import Foundation

public struct BencodeResponse<T: Codable> {
    public let info: T
    public let data: Data?
}

extension BencodeResponse: Equatable where T: Equatable {}

public enum Bencode {
    private enum Element: Character {
        case number0 = "0"
        case number1 = "1"
        case number2 = "2"
        case number3 = "3"
        case number4 = "4"
        case number5 = "5"
        case number6 = "6"
        case number7 = "7"
        case number8 = "8"
        case number9 = "9"
        case intIndicator = "i"
        case listIndicator = "l"
        case dictIndicator = "d"
        case endIndicator = "e"
        case separator = ":"
        
        init?(_ byte: UInt8?) {
            guard
                let byte: UInt8 = byte,
                let byteString: String = String(data: Data([byte]), encoding: .utf8),
                let character: Character = byteString.first,
                let result: Element = Element(rawValue: character)
            else { return nil }
            
            self = result
        }
    }
    
    private struct BencodeString {
        let value: String?
        let rawValue: Data
    }
    
    // MARK: - Functions
    
    public static func decodeResponse<T>(
        from data: Data,
        using dependencies: Dependencies = Dependencies()
    ) throws -> BencodeResponse<T> where T: Decodable {
        guard
            let decodedData: (value: Any, remainingData: Data) = decodeData(data),
            decodedData.remainingData.isEmpty == true,  // Ensure there is no left over data
            let resultArray: [Any] = decodedData.value as? [Any],
            resultArray.count > 0
        else { throw HTTPError.parsingFailed }
        
        return BencodeResponse(
            info: try Bencode.decode(T.self, decodedValue: resultArray[0], using: dependencies),
            data: {
                guard resultArray.count > 1 else { return nil }
                
                switch resultArray.last {
                    case let bencodeString as BencodeString: return bencodeString.rawValue
                    default: return resultArray.last as? Data
                }
            }()
        )
    }
    
    public static func decode<T: Decodable>(
        _ type: T.Type,
        from data: Data,
        using dependencies: Dependencies = Dependencies()
    ) throws -> T {
        guard
            let decodedData: (value: Any, remainingData: Data) = decodeData(data),
            decodedData.remainingData.isEmpty == true  // Ensure there is no left over data
        else { throw HTTPError.parsingFailed }
        
        return try Bencode.decode(T.self, decodedValue: decodedData.value, using: dependencies)
    }
    
    private static func decode<T: Decodable>(
        _ type: T.Type,
        decodedValue: Any,
        using dependencies: Dependencies = Dependencies()
    ) throws -> T {
        switch (decodedValue, T.self) {
            case (let directResult as T, _): return directResult
            case
                (let bencodeString as BencodeString, is String.Type),
                (let bencodeString as BencodeString, is Optional<String>.Type):
                return try (bencodeString.value as? T ?? { throw HTTPError.parsingFailed }())
                
            case (let bencodeString as BencodeString, _):
                return try bencodeString.rawValue.decoded(as: T.self, using: dependencies)
                
            default:
                guard
                    let jsonifiedInfo: Any = try? jsonify(decodedValue),
                    let infoData: Data = try? JSONSerialization.data(withJSONObject: jsonifiedInfo)
                else { throw HTTPError.parsingFailed }
                
                return try infoData.decoded(as: T.self, using: dependencies)
        }
    }
    
    // MARK: - Logic
    
    private static func decodeData(_ data: Data) -> (value: Any, remainingData: Data)? {
        switch Element(data.first) {
            case .number0, .number1, .number2, .number3, .number4,
                .number5, .number6, .number7, .number8, .number9:
                return decodeString(data)
                
            case .intIndicator: return decodeInt(data)
            case .listIndicator: return decodeList(data)
            case .dictIndicator: return decodeDict(data)
            default: return nil
        }
    }
    
    /// Decode a string element from iterator assumed to have structure `{length}:{data}`
    private static func decodeString(_ data: Data) -> (value: BencodeString, remainingData: Data)? {
        var mutableData: Data = data
        var lengthData: [UInt8] = []
        
        // Remove bytes until we hit the separator
        while let next: UInt8 = mutableData.popFirst(), Element(next) != .separator {
            lengthData.append(next)
        }
        
        // Need to reset the index of the data (it maintains the index after popping/slicing)
        // See https://forums.swift.org/t/data-subscript/57195 for more info
        mutableData = Data(mutableData)
        
        guard
            let lengthString: String = String(data: Data(lengthData), encoding: .ascii),
            let length: Int = Int(lengthString, radix: 10),
            mutableData.count >= length
        else { return nil }
        
        // Need to reset the index of the data (it maintains the index after popping/slicing)
        // See https://forums.swift.org/t/data-subscript/57195 for more info
        return (
            BencodeString(
                value: String(data: mutableData[0..<length], encoding: .ascii),
                rawValue: mutableData[0..<length]
            ),
            Data(mutableData.dropFirst(length))
        )
    }
    
    /// Decode an int element from iterator assumed to have structure `i{int}e`
    private static func decodeInt(_ data: Data) -> (value: Int, remainingData: Data)? {
        var mutableData: Data = data
        var intData: [UInt8] = []
        _ = mutableData.popFirst() // drop `i`
        
        // Pop until after `e`
        while let next: UInt8 = mutableData.popFirst(), Element(next) != .endIndicator {
            intData.append(next)
        }
        
        guard
            let intString: String = String(data: Data(intData), encoding: .ascii),
            let result: Int = Int(intString, radix: 10)
        else { return nil }
        
        // Need to reset the index of the data (it maintains the index after popping/slicing)
        // See https://forums.swift.org/t/data-subscript/57195 for more info
        return (result, Data(mutableData))
    }
    
    /// Decode a list element from iterator assumed to have structure `l{data}e`
    private static func decodeList(_ data: Data) -> ([Any], Data)? {
        var mutableData: Data = data
        var listElements: [Any] = []
        _ = mutableData.popFirst() // drop `l`
        
        while !mutableData.isEmpty, let next: UInt8 = mutableData.first, Element(next) != .endIndicator {
            guard let result = decodeData(mutableData) else { break }
                
            listElements.append(result.value)
            mutableData = result.remainingData
        }
        
        _ = mutableData.popFirst() // drop `e`
        
        // Need to reset the index of the data (it maintains the index after popping/slicing)
        // See https://forums.swift.org/t/data-subscript/57195 for more info
        return (listElements, Data(mutableData))
    }
    
    /// Decode a dict element from iterator assumed to have structure `d{data}e`
    private static func decodeDict(_ data: Data) -> ([String: Any], Data)? {
        var mutableData: Data = data
        var dictElements: [String: Any] = [:]
        _ = mutableData.popFirst() // drop `d`
        
        while !mutableData.isEmpty, let next: UInt8 = mutableData.first, Element(next) != .endIndicator {
            guard
                let keyResult = decodeString(mutableData),
                let key: String = keyResult.value.value,
                let valueResult = decodeData(keyResult.remainingData)
            else { return nil }
            
            dictElements[key] = valueResult.value
            mutableData = valueResult.remainingData
        }
        
        _ = mutableData.popFirst() // drop `e`
        
        // Need to reset the index of the data (it maintains the index after popping/slicing)
        // See https://forums.swift.org/t/data-subscript/57195 for more info
        return (dictElements, Data(mutableData))
    }
    
    // MARK: - Internal Functions
    
    private static func jsonify(_ value: Any) throws -> Any {
        switch value {
            case let arrayValue as [Any]: return try arrayValue.map { try jsonify($0) } as Any
            case let dictValue as [String: Any]: return try dictValue.mapValues { try jsonify($0) } as Any
            case let bencodeString as BencodeString: return bencodeString.value as Any
            default: return value
        }
    }
}
