# encoding: utf-8

import falcon
import ipaddress
import logging
import os
from certidude import config
from certidude.common import drop_privileges
from user_agents import parse
from wsgiref.simple_server import make_server, WSGIServer
from setproctitle import setproctitle

class NormalizeMiddleware(object):
    def process_request(self, req, resp, *args):
        req.context["remote_addr"] = ipaddress.ip_address(req.access_route[0])
        if req.user_agent:
            req.context["user_agent"] = parse(req.user_agent)
        else:
            req.context["user_agent"] = "Unknown user agent"


class App(object):
    PORT = 8080
    FORKS = None
    DROP_PRIVILEGES = True

    def __init__(self):
        app = falcon.API(middleware=NormalizeMiddleware())
        app.req_options.auto_parse_form_urlencoded = True
        self.attach(app)

        # Set up log handlers
        log_handlers = []
        if config.LOGGING_BACKEND == "sql":
            from certidude.mysqllog import LogHandler
            from certidude.api.log import LogResource
            uri = config.cp.get("logging", "database")
            log_handlers.append(LogHandler(uri))
        elif config.LOGGING_BACKEND == "syslog":
            from logging.handlers import SysLogHandler
            log_handlers.append(SysLogHandler())
            # Browsing syslog via HTTP is obviously not possible out of the box
        elif config.LOGGING_BACKEND:
            raise ValueError("Invalid logging.backend = %s" % config.LOGGING_BACKEND)
        from certidude.push import EventSourceLogHandler
        log_handlers.append(EventSourceLogHandler())

        for j in logging.Logger.manager.loggerDict.values():
            if isinstance(j, logging.Logger): # PlaceHolder is what?
                if j.name.startswith("certidude."):
                    j.setLevel(logging.DEBUG)
                    for handler in log_handlers:
                        j.addHandler(handler)

        self.server = make_server("127.0.1.1", self.PORT, app, WSGIServer)
        setproctitle("certidude: %s" % self.NAME)

    def run(self):
        if self.DROP_PRIVILEGES:
            drop_privileges()
        try:
            self.server.serve_forever()
        except KeyboardInterrupt:
            return
        else:
            return

    def fork(self):
        for j in range(self.FORKS):
            if not os.fork():
                self.run()
                return True
        return False



class ReadWriteApp(App):
    NAME = "backend server"

    def attach(self, app):
        from certidude import authority, config
        from certidude.tokens import TokenManager
        from .signed import SignedCertificateDetailResource
        from .request import RequestListResource, RequestDetailResource
        from .lease import LeaseResource, LeaseDetailResource
        from .script import ScriptResource
        from .tag import TagResource, TagDetailResource
        from .attrib import AttributeResource
        from .bootstrap import BootstrapResource
        from .token import TokenResource
        from .session import SessionResource, CertificateAuthorityResource
        from .revoked import RevokedCertificateDetailResource

        # Certificate authority API calls
        app.add_route("/api/certificate/", CertificateAuthorityResource())
        app.add_route("/api/signed/{cn}/", SignedCertificateDetailResource(authority))
        app.add_route("/api/request/{cn}/", RequestDetailResource(authority))
        app.add_route("/api/request/", RequestListResource(authority))
        app.add_route("/api/revoked/{serial_number}/", RevokedCertificateDetailResource(authority))

        token_resource = None
        token_manager = None
        if config.USER_ENROLLMENT_ALLOWED: # TODO: add token enable/disable flag for config
            if config.TOKEN_BACKEND == "sql":
                token_manager = TokenManager(config.TOKEN_DATABASE)
                token_resource = TokenResource(authority, token_manager)
                app.add_route("/api/token/", token_resource)
            elif not config.TOKEN_BACKEND:
                pass
            else:
                raise NotImplementedError("Token backend '%s' not supported" % config.TOKEN_BACKEND)

        app.add_route("/api/", SessionResource(authority, token_manager))

        # Extended attributes for scripting etc.
        app.add_route("/api/signed/{cn}/attr/", AttributeResource(authority, namespace="machine"))
        app.add_route("/api/signed/{cn}/script/", ScriptResource(authority))

        # API calls used by pushed events on the JS end
        app.add_route("/api/signed/{cn}/tag/", TagResource(authority))
        app.add_route("/api/signed/{cn}/lease/", LeaseDetailResource(authority))

        # API call used to delete existing tags
        app.add_route("/api/signed/{cn}/tag/{tag}/", TagDetailResource(authority))

        # Gateways can submit leases via this API call
        app.add_route("/api/lease/", LeaseResource(authority))

        # Bootstrap resource
        app.add_route("/api/bootstrap/", BootstrapResource(authority))

        # Add SCEP handler if we have any whitelisted subnets
        if config.SCEP_SUBNETS:
            from .scep import SCEPResource
            app.add_route("/api/scep/", SCEPResource(authority))
        return app


class ResponderApp(App):
    PORT = 8081
    FORKS = 4
    NAME = "ocsp responder"

    def attach(self, app):
        from certidude import authority
        from .ocsp import OCSPResource
        app.add_sink(OCSPResource(authority), prefix="/api/ocsp")
        return app


class RevocationListApp(App):
    PORT = 8082
    FORKS = 2
    NAME = "crl server"

    def attach(self, app):
        from certidude import authority
        from .revoked import RevocationListResource
        app.add_route("/api/revoked/", RevocationListResource(authority))
        return app


class BuilderApp(App):
    PORT = 8083
    FORKS = 1
    NAME = "image builder"

    def attach(self, app):
        # LEDE image builder resource
        from certidude import authority
        from .builder import ImageBuilderResource
        app.add_route("/api/build/{profile}/{suggested_filename}", ImageBuilderResource())
        return app


class LogApp(App):
    PORT = 8084
    FORKS = 2
    NAME = "log server"

    def attach(self, app):
        from certidude.api.log import LogResource
        uri = config.cp.get("logging", "database")
        app.add_route("/api/log/", LogResource(uri))
        return app