#!/usr/bin/env python3
"""Let colleagues sign in to your application: the smallest server for the guide "Let colleagues sign in to your
application". The browser holds this server's own session cookie and nothing else; the provider tokens, the consent
handle and the Ophiolite credential stay in the SDK's store on this server."""
import argparse
import json
import os
import secrets
from http.cookies import SimpleCookie
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qsl, urlsplit


def build(args):
    from ophiolite.oauth_app import ConsentRequired, FileStore, OAuthApp, SignInRequired
    # [guide:start]
    app = OAuthApp(args.workspace, args.application, args.client_id, args.public_origin + '/callback',
                   store=FileStore(args.store), client_secret=os.environ.get('OPHIOLITE_CLIENT_SECRET'))
    sessions = {}  # this server's own sessions: a random cookie value -> who signed in, and pending sign-ins

    def answer(path, query, session):
        if path == '/sign-in':                                  # 1. the identity provider (code + PKCE)
            address, session['pending'] = app.begin()
            return 302, address
        if path == '/callback' and 'code' in query:            # 2. Ophiolite's consent page
            session['person'] = app.callback(session.pop('pending', None), query.get('code'), query.get('state'))
            session['consent'] = secrets.token_urlsafe(24)
            return 302, app.consent_url(session['consent'])
        if path == '/callback':                                 # 3. the exchange, on this server
            app.consented(session.get('person'), session.pop('consent', None), query)
            return 302, '/'
        if path == '/projects' and session.get('person'):      # a call made for the person, from this server
            try:
                with app.client(session['person'], args.project) as client:
                    return 200, json.dumps({'wells': len(client.wells())})
            except (SignInRequired, ConsentRequired):
                return 302, '/sign-in'
        return 404, json.dumps({'message': 'Not found'})
    # [guide:end]
    return sessions, answer


def main(argv=None):
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--workspace', required=True, help='your Workspace address, for example https://workspace.example')
    parser.add_argument('--application', required=True, help="the application's registration id (Organization → Applications)")
    parser.add_argument('--client-id', required=True, help="the registration's client id; the secret comes from OPHIOLITE_CLIENT_SECRET")
    parser.add_argument('--public-origin', required=True, help="this server's own https address, registered as its return address")
    parser.add_argument('--project', required=True, help='the project the example reads')
    parser.add_argument('--store', default='people', help='private folder for each person’s sign-in (mode 0700)')
    parser.add_argument('--port', type=int, default=8080)
    args = parser.parse_args(argv)
    sessions, answer = build(args)

    class Handler(BaseHTTPRequestHandler):
        def do_GET(self):
            cookie = SimpleCookie(self.headers.get('Cookie') or '')
            key = cookie['session'].value if 'session' in cookie and cookie['session'].value in sessions else secrets.token_urlsafe(32)
            parts = urlsplit(self.path)
            status, body = answer(parts.path, dict(parse_qsl(parts.query)), sessions.setdefault(key, {}))
            self.send_response(status)
            self.send_header('Set-Cookie', 'session=' + key + '; Path=/; Secure; HttpOnly; SameSite=Lax')
            self.send_header('Cache-Control', 'no-store')
            if status == 302: self.send_header('Location', body); body = ''
            self.end_headers(); self.wfile.write(body.encode())

    ThreadingHTTPServer(('127.0.0.1', args.port), Handler).serve_forever()


if __name__ == '__main__':
    main()
