import debugModule from '../debug.mjs'
import AuthRequest from './auth-request.mjs'
import url from 'url'
import intoStream from 'into-stream'
import * as $rdf from 'rdflib'

const debug = debugModule.authentication
const ACL = $rdf.Namespace('http://www.w3.org/ns/auth/acl#')

export class SharingRequest extends AuthRequest {
  constructor (options) {
    super(options)
    this.authenticator = options.authenticator
    this.authMethod = options.authMethod
  }

  static fromParams (req, res) {
    const options = AuthRequest.requestOptions(req, res)
    return new SharingRequest(options)
  }

  static async get (req, res, next) {
    const request = SharingRequest.fromParams(req, res)
    const appUrl = request.getAppUrl()
    if (!appUrl) return next()
    const appOrigin = appUrl.origin
    const serverUrl = new url.URL(req.app.locals.ldp.serverUri)
    if (request.isUserLoggedIn()) {
      if (
        !request.isSubdomain(serverUrl.host, new url.URL(request.session.subject._id).host) ||
        (appUrl && request.isSubdomain(serverUrl.host, appUrl.host) && appUrl.protocol === serverUrl.protocol) ||
        await request.isAppRegistered(req.app.locals.ldp, appOrigin, request.session.subject._id)
      ) {
        request.setUserShared(appOrigin)
        request.redirectPostSharing()
      } else {
        request.renderForm(null, req, appOrigin)
      }
    } else {
      request.redirectPostSharing()
    }
  }

  static async share (req, res) {
    let accessModes = []
    let consented = false
    if (req.body) {
      accessModes = req.body.access_mode || []
      if (!Array.isArray(accessModes)) {
        accessModes = [accessModes]
      }
      consented = req.body.consent
    }
    const request = SharingRequest.fromParams(req, res)
    if (request.isUserLoggedIn()) {
      const appUrl = request.getAppUrl()
      const appOrigin = `${appUrl.protocol}//${appUrl.host}`
      debug('Sharing App')
      if (consented) {
        await request.registerApp(req.app.locals.ldp, appOrigin, accessModes, request.session.subject._id)
        request.setUserShared(appOrigin)
      }
      request.redirectPostSharing()
    } else {
      request.redirectPostSharing()
    }
  }

  isSubdomain (domain, subdomain) {
    const domainArr = domain.split('.')
    const subdomainArr = subdomain.split('.')
    for (let i = 1; i <= domainArr.length; i++) {
      if (subdomainArr[subdomainArr.length - i] !== domainArr[domainArr.length - i]) {
        return false
      }
    }
    return true
  }

  setUserShared (appOrigin) {
    if (!this.session.consentedOrigins) {
      this.session.consentedOrigins = []
    }
    if (!this.session.consentedOrigins.includes(appOrigin)) {
      this.session.consentedOrigins.push(appOrigin)
    }
  }

  isUserLoggedIn () {
    return !!(this.session.subject && this.session.subject._id)
  }

  getAppUrl () {
    if (!this.authQueryParams.redirect_uri) return
    return new url.URL(this.authQueryParams.redirect_uri)
  }

  async getProfileGraph (ldp, webId) {
    const store = $rdf.graph()
    const profileText = await ldp.readResource(webId)
    return new Promise((resolve, reject) => {
      $rdf.parse(profileText.toString(), store, this.getWebIdFile(webId), 'text/turtle', (error, kb) => {
        if (error) {
          reject(error)
        } else {
          resolve(kb)
        }
      })
    })
  }

  async saveProfileGraph (ldp, store, webId) {
    const text = $rdf.serialize(undefined, store, this.getWebIdFile(webId), 'text/turtle')
    await ldp.put(webId, intoStream(text), 'text/turtle')
  }

  getWebIdFile (webId) {
    const webIdurl = new url.URL(webId)
    return `${webIdurl.origin}${webIdurl.pathname}`
  }

  async isAppRegistered (ldp, appOrigin, webId) {
    const store = await this.getProfileGraph(ldp, webId)
    return store.each($rdf.sym(webId), ACL('trustedApp')).find((app) => {
      return store.each(app, ACL('origin')).find(rdfAppOrigin => rdfAppOrigin.value === appOrigin)
    })
  }

  async registerApp (ldp, appOrigin, accessModes, webId) {
    debug(`Registering app (${appOrigin}) with accessModes ${accessModes} for webId ${webId}`)
    const store = await this.getProfileGraph(ldp, webId)
    const origin = $rdf.sym(appOrigin)
    store.statementsMatching(null, ACL('origin'), origin).forEach(st => {
      store.removeStatements([...store.statementsMatching(null, ACL('trustedApp'), st.subject)])
      store.removeStatements([...store.statementsMatching(st.subject)])
    })
    const application = new $rdf.BlankNode()
    store.add($rdf.sym(webId), ACL('trustedApp'), application, new $rdf.NamedNode(webId))
    store.add(application, ACL('origin'), origin, new $rdf.NamedNode(webId))
    accessModes.forEach(mode => {
      store.add(application, ACL('mode'), ACL(mode))
    })
    await this.saveProfileGraph(ldp, store, webId)
  }

  postSharingUrl () {
    return this.authorizeUrl()
  }

  redirectPostSharing () {
    const uri = this.postSharingUrl()
    debug('Login successful, redirecting to ', uri)
    this.response.redirect(uri)
  }

  renderForm (error, req, appOrigin) {
    const queryString = (req && req.url && req.url.replace(/[^?]+\?/, '')) || ''
    const params = Object.assign({}, this.authQueryParams, {
      registerUrl: this.registerUrl(),
      returnToUrl: this.returnToUrl,
      enablePassword: this.localAuth.password,
      enableTls: this.localAuth.tls,
      tlsUrl: `/login/tls?${encodeURIComponent(queryString)}`,
      app_origin: appOrigin
    })
    if (error) {
      params.error = error.message
      this.response.status(error.statusCode)
    }
    this.response.render('auth/sharing', params)
  }
}

export default SharingRequest
