// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

const { NavigationInfo } = require('./browsingContextTypes')

/**
 * Represents the possible values for the SameSite attribute of a cookie.
 * @enum {string}
 */

const SameSite = {
  STRICT: 'strict',
  LAX: 'lax',
  NONE: 'none',

  findByName(name) {
    return (
      Object.values(this).find((type) => {
        return typeof type === 'string' && name.toLowerCase() === type.toLowerCase()
      }) || null
    )
  },
}

/**
 * Represents a BytesValue object.
 * Described in https://w3c.github.io/webdriver-bidi/#type-network-BytesValue.
 */
class BytesValue {
  static Type = {
    STRING: 'string',
    BASE64: 'base64',
  }

  /**
   * Creates a new BytesValue instance.
   * @param {string} type - The type of the BytesValue.
   * @param {string} value - The value of the BytesValue.
   */
  constructor(type, value) {
    this._type = type
    this._value = value
  }

  /**
   * Gets the type of the BytesValue.
   * @returns {string} The type of the BytesValue.
   */
  get type() {
    return this._type
  }

  /**
   * Gets the value of the BytesValue.
   * @returns {string} The value of the BytesValue.
   */
  get value() {
    return this._value
  }

  /**
   * Converts the BytesValue to a map.
   * @returns {Map<string, string>} A map representation of the BytesValue.
   */
  asMap() {
    const map = new Map()
    map.set('type', this._type)
    map.set('value', this._value)
    return map
  }
}

/**
 * Represents a header with a name and value.
 * Described in https://w3c.github.io/webdriver-bidi/#type-network-Header.
 */
class Header {
  /**
   * Creates a new Header instance.
   * @param {string} name - The name of the header.
   * @param {BytesValue} value - The value of the header.
   * @throws {Error} If the value is not an instance of BytesValue.
   */
  constructor(name, value) {
    this._name = name
    if (!(value instanceof BytesValue)) {
      throw new Error(`Value must be an instance of BytesValue. Received: '${value}'`)
    }
    this._value = value
  }

  /**
   * Gets the name of the header.
   * @returns {string} The name of the header.
   */
  get name() {
    return this._name
  }

  /**
   * Gets the value of the header.
   * @returns {BytesValue} The value of the header.
   */
  get value() {
    return this._value
  }
}

/**
 * Represents a cookie.
 * Described in https://w3c.github.io/webdriver-bidi/#type-network-Cookie.
 * @class
 */
class Cookie {
  constructor(name, value, domain, path, size, httpOnly, secure, sameSite, expires) {
    this._name = name
    this._value = value
    this._domain = domain
    this._path = path
    this._expires = expires
    this._size = size
    this._httpOnly = httpOnly
    this._secure = secure
    this._sameSite = sameSite
  }

  /**
   * Gets the name of the cookie.
   * @returns {string} The name of the cookie.
   */
  get name() {
    return this._name
  }

  /**
   * Gets the value of the cookie.
   * @returns {BytesValue} The value of the cookie.
   */
  get value() {
    return this._value
  }

  /**
   * Gets the domain of the cookie.
   * @returns {string} The domain of the cookie.
   */
  get domain() {
    return this._domain
  }

  /**
   * Gets the path of the cookie.
   * @returns {string} The path of the cookie.
   */
  get path() {
    return this._path
  }

  /**
   * Gets the expiration date of the cookie.
   * @returns {number} The expiration date of the cookie.
   */
  get expires() {
    return this._expires
  }

  /**
   * Gets the size of the cookie.
   * @returns {number} The size of the cookie.
   */
  get size() {
    return this._size
  }

  /**
   * Checks if the cookie is HTTP-only.
   * @returns {boolean} True if the cookie is HTTP-only, false otherwise.
   */
  get httpOnly() {
    return this._httpOnly
  }

  /**
   * Checks if the cookie is secure.
   * @returns {boolean} True if the cookie is secure, false otherwise.
   */
  get secure() {
    return this._secure
  }

  /**
   * Gets the same-site attribute of the cookie.
   * @returns {string} The same-site attribute of the cookie.
   */
  get sameSite() {
    return this._sameSite
  }
}

// No tests written for FetchTimingInfo. Must be updated after browsers implment it and corresponding WPT test are written.
/**
 * Represents the time of each part of the request.
 * Described in https://w3c.github.io/webdriver-bidi/#type-network-FetchTimingInfo.
 * @class
 */
class FetchTimingInfo {
  constructor(
    originTime,
    requestTime,
    redirectStart,
    redirectEnd,
    fetchStart,
    dnsStart,
    dnsEnd,
    connectStart,
    connectEnd,
    tlsStart,
    requestStart,
    responseStart,
    responseEnd,
  ) {
    this._originTime = originTime
    this._requestTime = requestTime
    this._redirectStart = redirectStart
    this._redirectEnd = redirectEnd
    this._fetchStart = fetchStart
    this._dnsStart = dnsStart
    this._dnsEnd = dnsEnd
    this._connectStart = connectStart
    this._connectEnd = connectEnd
    this._tlsStart = tlsStart
    this._requestStart = requestStart
    this._responseStart = responseStart
    this._responseEnd = responseEnd
  }

  /**
   * Gets the origin time.
   *
   * @returns {number} The origin time.
   */
  get originTime() {
    return this._originTime
  }

  /**
   * Get the request time.
   *
   * @returns {number} The request time.
   */
  get requestTime() {
    return this._requestTime
  }

  /**
   * Gets the timestamp when the redirect started.
   *
   * @returns {number} The timestamp when the redirect started.
   */
  get redirectStart() {
    return this._redirectStart
  }

  /**
   * Gets the timestamp when the redirect ended.
   *
   * @returns {number} The timestamp when the redirect ended.
   */
  get redirectEnd() {
    return this._redirectEnd
  }

  /**
   * Gets the timestamp when the fetch started.
   *
   * @returns {number} The timestamp when the fetch started.
   */
  get fetchStart() {
    return this._fetchStart
  }

  /**
   * Gets the timestamp when the domain lookup started.
   *
   * @returns {number} The timestamp when the domain lookup started.
   */
  get dnsStart() {
    return this._dnsStart
  }

  /**
   * Gets the timestamp when the domain lookup ended.
   *
   * @returns {number} The timestamp when the domain lookup ended.
   */
  get dnsEnd() {
    return this._dnsEnd
  }

  /**
   * Gets the timestamp when the connection started.
   *
   * @returns {number} The timestamp when the connection ended.
   */
  get connectStart() {
    return this._connectStart
  }

  /**
   * Gets the timestamp when the connection ended.
   *
   * @returns {number} The timestamp when the connection ended.
   */
  get connectEnd() {
    return this._connectEnd
  }

  /**
   * Gets the timestamp when the secure connection started.
   *
   * @returns {number} The timestamp when the secure connection started.
   */
  get tlsStart() {
    return this._tlsStart
  }

  /**
   * Gets the timestamp when the request started.
   *
   * @returns {number} The timestamp when the request started.
   */
  get requestStart() {
    return this._requestStart
  }

  /**
   * Gets the timestamp when the response started.
   *
   * @returns {number} The timestamp when the response started.
   */
  get responseStart() {
    return this._responseStart
  }

  /**
   * Gets the timestamp when the response ended.
   *
   * @returns {number} The timestamp when the response ended.
   */
  get responseEnd() {
    return this._responseEnd
  }
}

/**
 * Represents the data of a network request.
 * Described in https://w3c.github.io/webdriver-bidi/#type-network-RequestData.
 */
class RequestData {
  constructor(request, url, method, headers, cookies, headersSize, bodySize, timings) {
    this._request = request
    this._url = url
    this._method = method
    this._headers = []
    headers.forEach((header) => {
      let name = header.name
      let value = 'value' in header ? header.value : null

      this._headers.push(new Header(name, new BytesValue(value.type, value.value)))
    })

    this._cookies = []
    cookies.forEach((cookie) => {
      let name = cookie.name
      let domain = cookie.domain
      let path = cookie.path
      let size = cookie.size
      let httpOnly = cookie.httpOnly
      let secure = cookie.secure
      let sameSite = cookie.sameSite
      let value = 'value' in cookie ? cookie.value : null
      let expires = 'expires' in cookie ? cookie.expires : null

      this._cookies.push(new Cookie(name, value, domain, path, size, httpOnly, secure, sameSite, expires))
    })
    this._headersSize = headersSize
    this._bodySize = bodySize
    this._timings = new FetchTimingInfo(
      timings.originTime,
      timings.requestTime,
      timings.redirectStart,
      timings.redirectEnd,
      timings.fetchStart,
      timings.dnsStart,
      timings.dnsEnd,
      timings.connectStart,
      timings.connectEnd,
      timings.tlsStart,
      timings.requestStart,
      timings.responseStart,
      timings.responseEnd,
    )
  }

  /**
   * Get the request id.
   * @returns {string} The request id.
   */
  get request() {
    return this._request
  }

  /**
   * Get the URL of the request.
   * @returns {string} The URL of the request.
   */
  get url() {
    return this._url
  }

  /**
   * Get the HTTP method of the request.
   * @returns {string} The HTTP method of the request.
   */
  get method() {
    return this._method
  }

  /**
   * Get the headers of the request.
   * @returns {Header[]} An array of header objects.
   */
  get headers() {
    return this._headers
  }

  /**
   * Get the cookies of the request.
   * @returns {Cookie[]} An array of cookie objects.
   */
  get cookies() {
    return this._cookies
  }

  /**
   * Get the size of the headers in bytes.
   * @returns {number} The size of the headers in bytes.
   */
  get headersSize() {
    return this._headersSize
  }

  /**
   * Get the size of the request body in bytes.
   * @returns {number} The size of the request body in bytes.
   */
  get bodySize() {
    return this._bodySize
  }

  /**
   * Get the timing information of the request.
   * @returns {FetchTimingInfo} The timing information of the request.
   */
  get timings() {
    return this._timings
  }
}

/**
 * Represents the base parameters for a network request.
 * Described in https://w3c.github.io/webdriver-bidi/#type-network-BaseParameters.
 */
class BaseParameters {
  constructor(id, navigation, redirectCount, request, timestamp) {
    this._id = id
    this._navigation =
      navigation != null
        ? new NavigationInfo(navigation.context, navigation.navigation, navigation.timestamp, navigation.url)
        : null
    this._redirectCount = redirectCount
    this._request = new RequestData(
      request.request,
      request.url,
      request.method,
      request.headers,
      request.cookies,
      request.headersSize,
      request.bodySize,
      request.timings,
    )
    this._timestamp = timestamp
  }

  /**
   * Gets the browsing context ID of the network request.
   * @returns {string|null} The browsing context ID of the network request.
   */
  get id() {
    return this._id
  }

  /**
   * Gets the navigation information associated with the network request.
   * @returns {NavigationInfo|null} The navigation information associated with the network request, or null if not available.
   */
  get navigation() {
    return this._navigation
  }

  /**
   * Gets the number of redirects that occurred during the network request.
   * @returns {number} The number of redirects that occurred during the network request.
   */
  get redirectCount() {
    return this._redirectCount
  }

  /**
   * Gets the request data for the network request.
   * @returns {RequestData} The request data for the network request.
   */
  get request() {
    return this._request
  }

  /**
   * Gets the timestamp of the network request.
   * @returns {number} The timestamp of the network request.
   */
  get timestamp() {
    return this._timestamp
  }
}

/**
 * Represents source in the network.
 * Described in https://w3c.github.io/webdriver-bidi/#type-network-Initiator.
 */
class Initiator {
  /**
   * Constructs a new Initiator instance.
   * @param {string} type - The type of the initiator.
   * @param {number} columnNumber - The column number.
   * @param {number} lineNumber - The line number.
   * @param {string} stackTrace - The stack trace.
   * @param {string} request - The request id.
   */
  constructor(type, columnNumber, lineNumber, stackTrace, request) {
    this._type = type
    this._columnNumber = columnNumber
    this._lineNumber = lineNumber
    this._stackTrace = stackTrace
    this._request = request
  }

  /**
   * Gets the type of the initiator.
   * @returns {string} The type of the initiator.
   */
  get type() {
    return this._type
  }

  /**
   * Gets the column number.
   * @returns {number} The column number.
   */
  get columnNumber() {
    return this._columnNumber
  }

  /**
   * Gets the line number.
   * @returns {number} The line number.
   */
  get lineNumber() {
    return this._lineNumber
  }

  /**
   * Gets the stack trace.
   * @returns {string} The stack trace.
   */
  get stackTrace() {
    return this._stackTrace
  }

  /**
   * Gets the request ID.
   * @returns {string} The request ID.
   */
  get request() {
    return this._request
  }
}

/**
 * Represents the BeforeRequestSent event parameters.
 * @class
 * @extends BaseParameters
 * Described in https://w3c.github.io/webdriver-bidi/#event-network-beforeSendRequest.
 */
class BeforeRequestSent extends BaseParameters {
  constructor(id, navigation, redirectCount, request, timestamp, initiator) {
    super(id, navigation, redirectCount, request, timestamp)
    this._initiator = new Initiator(
      initiator.type,
      initiator.columnNumber,
      initiator.lineNumber,
      initiator.stackTrace,
      initiator.request,
    )
  }

  /**
   * Get the initiator of the request.
   * @returns {Initiator} The initiator object.
   */
  get initiator() {
    return this._initiator
  }
}

/**
 * Represents the FetchError event parameters.
 * Described https://w3c.github.io/webdriver-bidi/#event-network-fetchError
 * @extends BaseParameters
 */
class FetchError extends BaseParameters {
  /**
   * Creates a new FetchError instance.
   * @param {string} id - The ID of the error.
   * @param {string} navigation - The navigation information.
   * @param {number} redirectCount - The number of redirects.
   * @param {RequestData} request - The request object.
   * @param {number} timestamp - The timestamp of the error.
   * @param {string} errorText - The error text.
   */
  constructor(id, navigation, redirectCount, request, timestamp, errorText) {
    super(id, navigation, redirectCount, request, timestamp)
    this._errorText = errorText
  }

  /**
   * Gets the error text.
   * @returns {string} The error text.
   */
  get errorText() {
    return this._errorText
  }
}

/**
 * Represents the response data received from a network request.
 * Described in https://w3c.github.io/webdriver-bidi/#type-network-ResponseData.
 * @class
 */
class ResponseData {
  constructor(
    url,
    protocol,
    status,
    statusText,
    fromCache,
    headers,
    mimeType,
    bytesReceived,
    headersSize,
    bodySize,
    content,
  ) {
    this._url = url
    this._protocol = protocol
    this._status = status
    this._statusText = statusText
    this._fromCache = fromCache
    this._headers = headers
    this._mimeType = mimeType
    this._bytesReceived = bytesReceived
    this._headersSize = headersSize
    this._bodySize = bodySize
    this._content = content
  }

  /**
   * Get the URL.
   *
   * @returns {string} The URL.
   */
  get url() {
    return this._url
  }

  /**
   * Get the protocol.
   *
   * @returns {string} The protocol.
   */
  get protocol() {
    return this._protocol
  }

  /**
   * Get the HTTP status.
   *
   * @returns {string} The HTTP status.
   */
  get status() {
    return this._status
  }

  /**
   * Gets the status text.
   *
   * @returns {string} The status text.
   */
  get statusText() {
    return this._statusText
  }

  /**
   * Gets the value indicating whether the data is retrieved from cache.
   *
   * @returns {boolean} The value indicating whether the data is retrieved from cache.
   */
  get fromCache() {
    return this._fromCache
  }

  /**
   * Get the headers.
   *
   * @returns {Object} The headers object.
   */
  get headers() {
    return this._headers
  }

  /**
   * The MIME type of the network resource.
   *
   * @type {string}
   */
  get mimeType() {
    return this._mimeType
  }

  /**
   * Gets the number of bytes received.
   *
   * @returns {number} The number of bytes received.
   */
  get bytesReceived() {
    return this._bytesReceived
  }

  /**
   * Get the size of the headers.
   *
   * @returns {number} The size of the headers.
   */
  get headerSize() {
    return this._headersSize
  }

  /**
   * Get the size of the body.
   *
   * @returns {number} The size of the body.
   */
  get bodySize() {
    return this._bodySize
  }

  /**
   * Gets the content.
   *
   * @returns {any} The content.
   */
  get content() {
    return this._content
  }
}

/**
 * Represents the ResponseStarted event parameters.
 * Described in https://w3c.github.io/webdriver-bidi/#event-network-responseStarted.
 * @class
 * @extends BaseParameters
 */
class ResponseStarted extends BaseParameters {
  constructor(id, navigation, redirectCount, request, timestamp, response) {
    super(id, navigation, redirectCount, request, timestamp)
    this._response = new ResponseData(
      response.url,
      response.protocol,
      response.status,
      response.statusText,
      response.fromCache,
      response.headers,
      response.mimeType,
      response.bytesReceived,
      response.headerSize,
      response.bodySize,
      response.content,
    )
  }

  /**
   * Get the response data.
   * @returns {ResponseData} The response data.
   */
  get response() {
    return this._response
  }
}

module.exports = { Header, BytesValue, Cookie, SameSite, BeforeRequestSent, ResponseStarted, FetchError }
