import logging

from server.zeromq import sender
from server.model.db import Session
from server.model.models import Service, User, DVR, Stream, IO, Door

from server.svc_pb2 import Packet, \
    LOGIN_REQUEST, REQUEST_DATA, UPDATE_CONFIG, GENERATE_IO, \
    MONITOR

from . import manager as mon_mgr
from server.template import generate_html

from server.model import \
    create_service, edit_service, remove_service, \
    create_dvr, edit_dvr, remove_dvr, \
    create_stream, edit_stream, remove_stream, \
    create_io, edit_io, remove_io, \
    create_door, edit_door, remove_door, \
    update_service_dvrs_assoc, \
    remove_service_dvrs_assoc


class MonitorHandler(object):
    def send_login_request(self, mon_id):
        rep = Packet()
        rep.client_id = mon_id
        rep.client_type = MONITOR
        rep.command.type = LOGIN_REQUEST
        sender.broadcast_monitors(rep.SerializeToString())

    def process_hello(self, pkt):
        mon_id = mon_mgr.create()
        logging.info(f'New MON connected, id={mon_id}')

        rep = Packet()
        rep.client_id = mon_id
        rep.client_type = MONITOR
        rep.hello.to_id = mon_id
        sender.broadcast_monitors(rep.SerializeToString())

    def process_login(self, pkt):
        mon_id = pkt.client_id
        mon = mon_mgr.get(mon_id)
        if mon is None:
            logging.error('MON requested Login without HELLO!')
            return

        login = pkt.login
        username = login.username
        password = login.password

        rep = Packet()
        rep.client_id = pkt.client_id
        rep.client_type = MONITOR

        session = Session()
        user = session \
            .query(User) \
            .filter_by(username=username, password=password) \
            .first()

        if user is None:
            logging.error(f'User not found: {username}')
            rep.login_resp.success = False
            mon.set_login(False)
        else:
            logging.info(f'Found user: {username}')
            rep.login_resp.success = True
            mon.set_login(True)

        sender.broadcast_monitors(rep.SerializeToString())

    def process_data(self, pkt):
        mon_id = pkt.client_id
        mon = mon_mgr.get(mon_id)

        rep = Packet()
        rep.client_id = mon_id
        rep.client_type = MONITOR

        if mon is None:
            logging.error('MON was not logged in but request for data')
            self.send_login_request(mon_id)
            return

        session = Session()
        dvrs = session.query(DVR).all()
        for d in dvrs:
            dvr = rep.data.dvrs.add()
            dvr.id = d.id
            dvr.name = d.name
            dvr.ip = d.ip
            dvr.user = d.user
            dvr.password = d.password

        streams = session.query(Stream).all()
        for s in streams:
            stm = rep.data.streams.add()
            stm.id = s.id
            stm.name = s.name
            stm.dvr_id = s.dvr.id
            stm.dvr_name = s.dvr.name
            stm.in_port = s.in_port
            stm.out_port = s.out_port
            stm.channels = s.channels
            stm.type = s.type

        ios = session.query(IO).all()
        for i in ios:
            io = rep.data.ios.add()
            io.id = i.id
            io.name = i.name
            io.dvr_id = i.dvr.id
            io.dvr_name = i.dvr.name
            io.port = i.port
            io.state = i.state
            io.enabled = i.enabled
            io.events_enabled = i.events_enabled

        doors = session.query(Door).all()
        for d in doors:
            door = rep.data.doors.add()
            door.id = d.id
            door.name = d.name
            door.io_id = d.io.id
            door.io_name = d.io.name
            door.in_pin = d.in_pin
            door.out_pin = d.out_pin
            door.delay = d.delay
            door.nonc = d.nonc

        services = session.query(Service).all()
        for s in services:
            svc = rep.data.services.add()
            svc.id = s.id
            svc.name = s.name
            svc.client_id = s.client_id
            svc.hostname = s.hostname
            svc.html_path = s.html_path
            svc.event_port = s.event_port
            svc.active = s.active

            assoc = rep.data.services_dvrs.add()
            assoc.service_id = s.id
            for d in s.dvrs.all():
                assoc.dvr_ids.append(d.id)

        sender.broadcast_monitors(rep.SerializeToString())

    def process_create(self, pkt):
        mon_id = pkt.client_id
        mon = mon_mgr.get(mon_id)

        if mon is None:
            logging.error('MON not logged in but trying to create data')
            self.send_login_request(mon_id)
            return

        _type = pkt.create.WhichOneof('oneof_data')
        _is_edit = pkt.create.is_edit

        if _type == 'service':
            svc = pkt.create.service
            if _is_edit:
                edit_service(svc)
            else:
                create_service(svc)
        elif _type == 'dvr':
            dvr = pkt.create.dvr
            if _is_edit:
                edit_dvr(dvr)
            else:
                create_dvr(dvr)
        elif _type == 'stream':
            stm = pkt.create.stream
            if _is_edit:
                edit_stream(stm)
            else:
                create_stream(stm)
        elif _type == 'io':
            io = pkt.create.io
            if _is_edit:
                edit_io(io)
            else:
                create_io(io)
        elif _type == 'door':
            door = pkt.create.door
            if _is_edit:
                edit_door(door)
            else:
                create_door(door)
        elif _type == 'service_dvr':
            assoc = pkt.create.service_dvr
            svc_id = assoc.service_id
            dvr_ids = [dvr_id for dvr_id in assoc.dvr_ids]
            update_service_dvrs_assoc(svc_id, dvr_ids)
        else:
            logging.error(f'type {_type} is undefined')

    def process_remove(self, pkt):
        _remove = pkt.remove.WhichOneof('oneof_data')
        if _remove == 'service':
            remove_service_dvrs_assoc(pkt.remove.service.id)
            remove_service(pkt.remove.service.id)
        elif _remove == 'dvr':
            remove_dvr(pkt.remove.dvr.id)
        elif _remove == 'stream':
            remove_stream(pkt.remove.stream.id)
        elif _remove == 'io':
            remove_io(pkt.remove.io.id)
        elif _remove == 'door':
            remove_door(pkt.remove.door.id)

    def process_generate(self, pkt):
        session = Session()

        services = []
        if len(pkt.command.int_rs) == 0:
            services = session \
                .query(Service) \
                .all()
        else:
            services = session \
                .query(Service) \
                .filter(Service.id.in_(pkt.command.int_rs)) \
                .all()

        logging.info(f'Pushing layouts to: {services}')

        for svc in services:
            dvrs = svc.dvrs

            for dvr in dvrs:
                ios = dvr.ios

                for io in ios:
                    html = generate_html(io.id)

                    cmd = Packet()
                    cmd.client_id = svc.client_id
                    cmd.command.type = GENERATE_IO
                    cmd.command.str_rs.append(f'{io.id}')
                    cmd.command.str_rs.append(html)
                    sender.broadcast_services(cmd.SerializeToString())

        resp = Packet()
        resp.client_id = pkt.client_id
        resp.client_type = MONITOR
        resp.command_resp.type = GENERATE_IO
        sender.broadcast_monitors(resp.SerializeToString())

    def process_config(self, pkt):
        session = Session()

        services = []
        if len(pkt.command.int_rs) == 0:
            services = session \
                .query(Service) \
                .all()
        else:
            services = session \
                .query(Service) \
                .filter(Service.id.in_(pkt.command.int_rs)) \
                .all()

        logging.info(f'Pushing configs to: {services}')

        for svc in services:
            cid = svc.client_id

            rep = Packet()
            rep.client_id = cid

            dvrs = svc.dvrs.all()
            for d in dvrs:
                dvr = rep.data.dvrs.add()
                dvr.id = d.id
                dvr.name = d.name
                dvr.ip = d.ip
                dvr.user = d.user
                dvr.password = d.password

                streams = d.streams
                for s in streams:
                    stm = rep.data.streams.add()
                    stm.id = s.id
                    stm.name = s.name
                    stm.dvr_id = s.dvr.id
                    stm.dvr_name = s.dvr.name
                    stm.in_port = s.in_port
                    stm.out_port = s.out_port
                    stm.channels = s.channels
                    stm.type = s.type

                ios = d.ios
                for i in ios:
                    io = rep.data.ios.add()
                    io.id = i.id
                    io.name = i.name
                    io.dvr_id = i.dvr.id
                    io.dvr_name = i.dvr.name
                    io.port = i.port
                    io.state = i.state
                    io.enabled = i.enabled
                    io.events_enabled = i.events_enabled

                    doors = i.doors
                    for d in doors:
                        door = rep.data.doors.add()
                        door.id = d.id
                        door.name = d.name
                        door.io_id = d.io.id
                        door.io_name = d.io.name
                        door.in_pin = d.in_pin
                        door.out_pin = d.out_pin
                        door.delay = d.delay
                        door.nonc = d.nonc

            sender.broadcast_services(rep.SerializeToString())

        resp = Packet()
        resp.client_id = pkt.client_id
        resp.client_type = MONITOR
        resp.command_resp.type = UPDATE_CONFIG
        sender.broadcast_monitors(resp.SerializeToString())

    def process_command(self, pkt):
        cmd = pkt.command
        type = cmd.type
        logging.info(f'MON command: {type}')
        if type == REQUEST_DATA:
            self.process_data(pkt)
        elif type == UPDATE_CONFIG:
            self.process_config(pkt)
        elif type == GENERATE_IO:
            self.process_generate(pkt)


handler = MonitorHandler()


def process_mon_command(pkt):
    cmd_type = pkt.WhichOneof('oneof_data')

    logging.info(f'MON command: {cmd_type}')
    if cmd_type == 'hello':
        handler.process_hello(pkt)
    elif cmd_type == 'login':
        handler.process_login(pkt)
    elif cmd_type == 'create':
        handler.process_create(pkt)
    elif cmd_type == 'remove':
        handler.process_remove(pkt)
    elif cmd_type == 'command':
        handler.process_command(pkt)
