import https from 'https'
import pipe from 'multipipe'
import { Transform, compose } from 'stream'
import config from './config.mjs'

export default class Proxy {
  constructor(opts = {}) {
    Object.assign(this, {
      agent: opts.agent || new https.Agent({
        keepAlive: true,
        maxSockets: 2,
        keepAliveMsecs: 1000 * 60 * 60,
        secureProtocol: 'TLSv1_2_method',
        rejectUnauthorized: false,
      }),
      cache: new Map(),
      defaultRemap: 'default',
      remap: new Map([
        ['default', '/frettatengt/serstok-malefni/taktu-tvaer/']
      ])
    })
  }

  register(server) {
    server.flaska.on404(this.proxy.bind(this))
  }

  getTime(time) {
    if (!time) return 60
    let m = time.match(/max-age=(\d+)/)
    if (!m) return 60
    return Number(m[1]) || 60
  }

  getVaryKey(req, vary) {
    if (!vary) {
      return 'null'
    }
    let out = []
    for (let v of vary.split(',')) {
      out.push(req.headers[v.trim().toLowerCase()] || 'null')
    }
    return out.join(', ')
  }

  cleanHeaders(headers) {
    var set = new Set()
    for (let key of Object.keys(headers)) {
      if (set.has(key.toLowerCase())) {
        delete headers[key]
      } else {
        set.add(key)
      }
    }
    return headers
  }

  cloneHeaders(headers) {
    var out = {}
    for (let key of Object.keys(headers)) {
      if (key !== 'content-length' && key !== 'content-type') {
        out[key] = headers[key]
      }
    }
    return out
  }

  proxyCache(req, path, time, res) {
    let cache = this.cache.get(path)
    if (!cache) {
      this.cache.set(path, cache = {
        path: path,
        time: this.getTime(time),
        created: new Date(),
        status: res.statusCode,
        type: res.headers['content-type'],
        vary: (res.headers['vary'] || '').toLowerCase(),
        data: new Map()
      })
    }

    let varyKey = this.getVaryKey(req, cache.vary)

    if (cache.data.has(varyKey)) return res

    let item = {
      headers: this.cleanHeaders(res.headers),
      data: [],
      done: false,
    }
    cache.data.set(varyKey, item)

    const capture = new Transform({
      decodeStrings: false,
      destroy(err, cb) {
        if (!err) {
          if (item.data.length) {
            item.data = Buffer.concat(item.data)
            item.done = true
          } else {
            cache.data.delete(varyKey, item)
          }
        }
        cb(err)
      },

      transform(chunk, encoding, callback) {
        item.data.push(chunk)
        callback(null, chunk);
      },
    });

    return pipe(res, capture)
  }

  getCache(req, path) {
    let cache = this.cache.get(path)
    if (!cache) return null
    if ((new Date() - cache.created) / 1000 > cache.time) {
      this.cache.delete(path)
      return null
    }
    let varyKey = this.getVaryKey(req, cache.vary)
    let data = cache.data.get(varyKey)
    if (!data) return null
    if (!data.done) return null

    return {
      varyKey: varyKey,
      res: cache,
      data: cache.data.get(varyKey)
    }
  }

  async proxy(ctx) {
    let url = ctx.req.url
    if (!url.startsWith('/_')) {
      url = this.remap.get(ctx.req.headers['host'])
      if (!url) {
        url = this.remap.get(this.defaultRemap)
      }
      url += ctx.req.url.slice(1)
    }
    let cache = this.getCache(ctx.req, url)

    if (cache) {
      ctx.status = cache.res.status
      ctx.type = cache.res.type
      ctx.body = cache.data.data
      ctx.headers = this.cloneHeaders(cache.data.headers)
      console.log('found cache', cache)
      return
    }

    ctx.req.headers.host = 'www.sa.is'

    var res = await new Promise((res, rej) => {
      var req = https.request({
        hostname: 'www.sa.is',
        path: url,
        method: ctx.req.method,
        headers: ctx.req.headers,
        agent: this.agent,
      }, results => {
        res(results)
      })
      req.on('error', rej)
      req.end()
    })

    ctx.status = res.statusCode
    ctx.headers = this.cloneHeaders(res.headers)
    ctx.type = res.headers['content-type']
    ctx.body = this.proxyCache(ctx.req, url, ctx.headers['cache-control'], res)
  }
}
