//
//  NostrResponse.swift
//  damus
//
//  Created by William Casarin on 2022-04-11.
//

import Foundation

struct CommandResult {
    let event_id: NoteId
    let ok: Bool
    let msg: String
}

enum MaybeResponse {
    case bad
    case ok(NostrResponse)
}

enum NegentropyResponse {
    /// Negentropy error
    case error(subscriptionId: String, reasonCodeString: String)
    /// Negentropy message
    case message(subscriptionId: String, data: [UInt8])
    /// Invalid negentropy message
    case invalidResponse(subscriptionId: String)
    
    var subscriptionId: String {
        switch self {
        case .error(subscriptionId: let subscriptionId, reasonCodeString: let reasonCodeString): subscriptionId
        case .message(subscriptionId: let subscriptionId, data: let data): subscriptionId
        case .invalidResponse(subscriptionId: let subscriptionId): subscriptionId
        }
    }
}

enum NostrResponse {
    case event(String, NostrEvent)
    case notice(String)
    case eose(String)
    case ok(CommandResult)
    /// An [NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md) `auth` challenge.
    ///
    /// The associated type of this case is the challenge string sent by the server.
    case auth(String)
    /// Negentropy error
    case negentropyError(subscriptionId: String, reasonCodeString: String)
    /// Negentropy message
    case negentropyMessage(subscriptionId: String, hexEncodedData: String)

    var subid: String? {
        switch self {
        case .ok:
            return nil
        case .event(let sub_id, _):
            return sub_id
        case .eose(let sub_id):
            return sub_id
        case .notice(_):
            return nil
        case .auth(let challenge_string):
            return challenge_string
        case .negentropyError(subscriptionId: let subscriptionId, reasonCodeString: _):
            return subscriptionId
        case .negentropyMessage(subscriptionId: let subscriptionId, hexEncodedData: _):
            return subscriptionId
        }
    }
    
    var negentropyResponse: NegentropyResponse? {
        switch self {
        case .event(_, _): return nil
        case .notice(_): return nil
        case .eose(_): return nil
        case .ok(_): return nil
        case .auth(_): return nil
        case .negentropyError(subscriptionId: let subscriptionId, reasonCodeString: let reasonCodeString):
            return .error(subscriptionId: subscriptionId, reasonCodeString: reasonCodeString)
        case .negentropyMessage(subscriptionId: let subscriptionId, hexEncodedData: let hexData):
            if let bytes = hex_decode(hexData) {
                return .message(subscriptionId: subscriptionId, data: bytes)
            }
            return .invalidResponse(subscriptionId: subscriptionId)
        }
    }
    
    /// Decode a Nostr response from JSON using idiomatic Swift parsing
    /// Supports NEG-MSG and NEG-ERR formats, falling back to C parsing for other message types
    static func decode(from json: String) -> NostrResponse? {
        // Try Swift-based parsing first for negentropy messages
        if let response = try? decodeNegentropyMessage(from: json) {
            return response
        }
        
        // Fall back to C-based parsing for standard Nostr messages
        return owned_from_json(json: json)
    }
    
    /// Decode negentropy messages using idiomatic Swift
    private static func decodeNegentropyMessage(from json: String) throws -> NostrResponse? {
        guard let jsonData = json.data(using: .utf8) else {
            return nil
        }
        
        guard let jsonArray = try JSONSerialization.jsonObject(with: jsonData) as? [Any],
              jsonArray.count >= 2,
              let messageType = jsonArray[0] as? String else {
            return nil
        }
        
        switch messageType {
        case "NEG-MSG":
            // Format: ["NEG-MSG", "subscription-id", "hex-encoded-data"]
            guard jsonArray.count == 3,
                  let subscriptionId = jsonArray[1] as? String,
                  let hexData = jsonArray[2] as? String else {
                return nil
            }
            return .negentropyMessage(subscriptionId: subscriptionId, hexEncodedData: hexData)
            
        case "NEG-ERR":
            // Format: ["NEG-ERR", "subscription-id", "reason-code"]
            guard jsonArray.count == 3,
                  let subscriptionId = jsonArray[1] as? String,
                  let reasonCode = jsonArray[2] as? String else {
                return nil
            }
            return .negentropyError(subscriptionId: subscriptionId, reasonCodeString: reasonCode)
            
        default:
            // Not a negentropy message
            return nil
        }
    }

    private static func owned_from_json(json: String) -> NostrResponse? {
        return json.withCString{ cstr in
            let bufsize: Int = max(Int(Double(json.utf8.count) * 8.0), Int(getpagesize()))
            let data = malloc(bufsize)

            if data == nil {
                let r: NostrResponse? = nil
                return r
            }
            //guard var json_cstr = json.cString(using: .utf8) else { return nil }

            //json_cs
            var tce = ndb_tce()

            let len = ndb_ws_event_from_json(cstr, Int32(json.utf8.count), &tce, data, Int32(bufsize), nil)
            if len <= 0 {
                free(data)
                return nil
            }

            switch tce.evtype {
            case NDB_TCE_OK:
                defer { free(data) }

                guard let evid_str = sized_cstr(cstr: tce.subid, len: tce.subid_len),
                      let evid = hex_decode_noteid(evid_str),
                      let msg  = sized_cstr(cstr: tce.command_result.msg, len: tce.command_result.msglen) else {
                    return nil
                }
                let cr = CommandResult(event_id: evid, ok: tce.command_result.ok == 1, msg: msg)

                return .ok(cr)
            case NDB_TCE_EOSE:
                defer { free(data) }

                guard let subid = sized_cstr(cstr: tce.subid, len: tce.subid_len) else {
                    return nil
                }
                return .eose(subid)
            case NDB_TCE_EVENT:

                // Create new Data with just the valid bytes
                guard let note_data = realloc(data, Int(len)) else {
                    free(data)
                    return nil
                }
                let new_note = ndb_note_ptr(ptr: OpaquePointer(note_data))
                let note = NdbNote(note: new_note, size: Int(len), owned: true, key: nil)

                guard let subid = sized_cstr(cstr: tce.subid, len: tce.subid_len) else {
                    free(data)
                    return nil
                }
                return .event(subid, note)
            case NDB_TCE_NOTICE:
                free(data)
                return .notice("")
            case NDB_TCE_AUTH:
                defer { free(data) }

                guard let challenge_string = sized_cstr(cstr: tce.subid, len: tce.subid_len) else {
                    return nil
                }
                return .auth(challenge_string)
            default:
                free(data)
                return nil
            }
        }
    }
}

func sized_cstr(cstr: UnsafePointer<CChar>, len: Int32) -> String? {
    let msgbuf = Data(bytes: cstr, count: Int(len))
    return String(data: msgbuf, encoding: .utf8)
}

