import Foundation
import NetworkExtension
import OpenVPNAdapter

extension PacketTunnelProvider: OpenVPNAdapterDelegate {
  // OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
  // `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
  // protocol if the tunnel is configured without errors. Otherwise send nil.
  // `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
  // you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
  // send `self.packetFlow` to `completionHandler` callback.
  func openVPNAdapter(
    _ openVPNAdapter: OpenVPNAdapter,
    configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?,
    completionHandler: @escaping (Error?) -> Void
  ) {
    // In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
    // send empty string to NEDNSSettings.matchDomains
    networkSettings?.dnsSettings?.matchDomains = [""]

    if splitTunnelType == "1" {
      var ipv4IncludedRoutes = [NEIPv4Route]()
      let STSdata = Data(splitTunnelSites!.utf8)
      do {
        guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
        for allowedIPString in STSArray {
          if let allowedIP = IPAddressRange(from: allowedIPString) {
            ipv4IncludedRoutes.append(NEIPv4Route(
              destinationAddress: "\(allowedIP.address)",
              subnetMask: "\(allowedIP.subnetMask())"))
          }
        }
      } catch {
        wg_log(.error, message: "Parse JSONSerialization Error")
      }
      networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
    } else {
      if splitTunnelType == "2" {
        var ipv4ExcludedRoutes = [NEIPv4Route]()
        var ipv4IncludedRoutes = [NEIPv4Route]()
        var ipv6IncludedRoutes = [NEIPv6Route]()
        let STSdata = Data(splitTunnelSites!.utf8)
        do {
          guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
          for excludeIPString in STSArray {
            if let excludeIP = IPAddressRange(from: excludeIPString) {
              ipv4ExcludedRoutes.append(NEIPv4Route(
                destinationAddress: "\(excludeIP.address)",
                subnetMask: "\(excludeIP.subnetMask())"))
            }
          }
        } catch {
          wg_log(.error, message: "Parse JSONSerialization Error")
        }
        if let allIPv4 = IPAddressRange(from: "0.0.0.0/0") {
          ipv4IncludedRoutes.append(NEIPv4Route(
            destinationAddress: "\(allIPv4.address)",
            subnetMask: "\(allIPv4.subnetMask())"))
        }
        if let allIPv6 = IPAddressRange(from: "::/0") {
          ipv6IncludedRoutes.append(NEIPv6Route(
            destinationAddress: "\(allIPv6.address)",
            networkPrefixLength: NSNumber(value: allIPv6.networkPrefixLength)))
        }
        networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
        networkSettings?.ipv6Settings?.includedRoutes = ipv6IncludedRoutes
        networkSettings?.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes
      }
    }

    // Set the network settings for the current tunneling session.
    setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
  }

  // Process events returned by the OpenVPN library
  func openVPNAdapter(
    _ openVPNAdapter: OpenVPNAdapter,
    handleEvent event: OpenVPNAdapterEvent,
    message: String?) {
      switch event {
      case .connected:
        if reasserting {
          reasserting = false
        }

        guard let startHandler = startHandler else { return }

        startHandler(nil)
        self.startHandler = nil
      case .disconnected:
        guard let stopHandler = stopHandler else { return }

        if vpnReachability.isTracking {
          vpnReachability.stopTracking()
        }

        stopHandler()
        self.stopHandler = nil
      case .reconnecting:
        reasserting = true
      default:
        break
      }
    }

  // Handle errors thrown by the OpenVPN library
  func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
    // Handle only fatal errors
    guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
          fatal == true else { return }

    if vpnReachability.isTracking {
      vpnReachability.stopTracking()
    }

    if let startHandler {
      startHandler(error)
      self.startHandler = nil
    } else {
      cancelTunnelWithError(error)
    }
  }

  // Use this method to process any log message returned by OpenVPN library.
  func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
    // Handle log messages
    wg_log(.info, message: logMessage)
  }
}
