import logging

from .db import engine, Base, Session
from .models import Service, DVR, Stream, IO, Door

from server.cmd_common import notify_mons_service_created, \
    notify_mons_service_removed, \
    notify_mons_dvr_created, \
    notify_mons_dvr_removed, \
    notify_mons_stream_created, \
    notify_mons_stream_removed, \
    notify_mons_io_created, \
    notify_mons_io_removed, \
    notify_mons_door_created, \
    notify_mons_door_removed, \
    notify_mons_assoc_created


def setup():
    Base.metadata.create_all(bind=engine)


def create_service(proto_svc):
    session = Session()
    svc = Service(proto_svc.name,
                  proto_svc.client_id,
                  proto_svc.hostname,
                  proto_svc.html_path,
                  proto_svc.event_port,
                  proto_svc.active)

    session.add(svc)

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Adding Service error: {}'.format(e))
        rs = False

    notify_mons_service_created(svc, rs)


def edit_service(proto_svc):
    session = Session()
    svc = session.query(Service).filter_by(id=proto_svc.id).first()
    if svc is None:
        logging.error(f"Couldn't find Service for edit {proto_svc.id}")
        return

    svc.name = proto_svc.name
    svc.client_id = proto_svc.client_id

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Editing Service error: {}'.format(e))
        rs = False

    notify_mons_service_created(svc, rs)


def remove_service(id):
    session = Session()
    session.query(Service).filter_by(id=id).delete()

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Deleting Service error: {}'.format(e))
        rs = False

    notify_mons_service_removed(id, rs)


def create_dvr(proto_dvr):
    session = Session()
    dvr = DVR(proto_dvr.name, proto_dvr.ip, proto_dvr.user, proto_dvr.password)
    session.add(dvr)

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Adding DVR error: {}'.format(e))
        rs = False

    notify_mons_dvr_created(dvr, rs)


def edit_dvr(proto_dvr):
    session = Session()
    dvr = session.query(DVR).filter_by(id=proto_dvr.id).first()
    if dvr is None:
        logging.error(f"Couldn't find DVR for edit: {proto_dvr.id}")
        return

    dvr.name = proto_dvr.name
    dvr.ip = proto_dvr.ip
    dvr.user = proto_dvr.user
    dvr.password = proto_dvr.password

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Editing DVR error: {}'.format(e))
        rs = False

    notify_mons_dvr_created(dvr, rs)


def remove_dvr(id):
    session = Session()
    query = session.query(DVR).filter_by(id=id)
    dvr = query.first()
    if dvr is None:
        logging.error(f'Removing DVR error: DVR not found - {id}')
        return

    remove_streams_by_dvr(dvr.id)
    remove_ios_by_dvr(dvr.id)

    query.delete()

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Deleting DVR error: {}'.format(e))
        rs = False

    notify_mons_dvr_removed(id, rs)


def create_stream(proto_stm):
    session = Session()
    dvr = session.query(DVR).filter_by(id=proto_stm.dvr_id).first()
    if dvr is None:
        logging.error('Adding Stream error: DVR {} not found!'.format(proto_stm.dvr_id))
        return

    stm = Stream(proto_stm.name,
                 dvr.id,
                 proto_stm.in_port,
                 proto_stm.out_port,
                 proto_stm.channels,
                 proto_stm.type)
    session.add(stm)

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Adding Stream error: {}'.format(e))
        rs = False

    notify_mons_stream_created(stm, rs)


def edit_stream(proto_stm):
    session = Session()
    dvr = session.query(DVR).filter_by(id=proto_stm.dvr_id).first()
    if dvr is None:
        logging.error(f'Editing Stream error: DVR {proto_stm.dvr_id} not found!')
        return

    stm = session.query(Stream).filter_by(id=proto_stm.id).first()
    stm.name = proto_stm.name
    stm.dvr_id = dvr.id
    stm.in_port = proto_stm.in_port
    stm.out_port = proto_stm.out_port
    stm.channels = proto_stm.channels
    stm.type = proto_stm.type

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Editing Stream error: {}'.format(e))
        rs = False

    notify_mons_stream_created(stm, rs)


def remove_stream(id):
    session = Session()
    session.query(Stream).filter_by(id=id).delete()

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Deleting Stream error: {}'.format(e))
        rs = False

    notify_mons_stream_removed(id, rs)


def create_io(proto_io):
    session = Session()
    dvr = session.query(DVR).filter_by(id=proto_io.dvr_id).first()
    if dvr is None:
        logging.error(f'Adding IO error: DVR {proto_io.dvr_id} not found!')
        return

    io = IO(proto_io.name,
            dvr.id,
            proto_io.port,
            proto_io.state,
            proto_io.enabled,
            proto_io.events_enabled)
    session.add(io)

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Adding IO error: {}'.format(e))
        rs = False

    notify_mons_io_created(io, rs)


def edit_io(proto_io):
    session = Session()
    dvr = session.query(DVR).filter_by(id=proto_io.dvr_id).first()
    if dvr is None:
        logging.error(f'Editing IO error: DVR {proto_io.dvr_id} not found!')
        return

    io = session.query(IO).filter_by(id=proto_io.id).first()
    io.name = proto_io.name
    io.dvr_id = dvr.id
    io.port = proto_io.port
    io.state = proto_io.state
    io.enabled = proto_io.enabled
    io.events_enabled = proto_io.events_enabled

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Editing IO error: {}'.format(e))
        rs = False

    notify_mons_io_created(io, rs)


def remove_io(id):
    session = Session()
    query = session.query(IO).filter_by(id=id)
    io = query.first()
    if io is None:
        logging.error(f'Deleting io erro: IO not found - {id}')
        return

    remove_doors_by_io(io.id)
    query.delete()

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Deleting IO error: {}'.format(e))
        rs = False

    notify_mons_io_removed(id, rs)


def create_door(proto_door):
    session = Session()
    io = session.query(IO).filter_by(id=proto_door.io_id).first()
    if io is None:
        logging.error(f'Adding Door error: IO {proto_door.io_id} not found!')
        return

    door = Door(proto_door.name,
                io.id,
                proto_door.in_pin,
                proto_door.out_pin,
                proto_door.delay,
                proto_door.nonc)
    session.add(door)

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Adding Door error: {}'.format(e))
        rs = False

    notify_mons_door_created(door, rs)


def edit_door(proto_door):
    session = Session()
    io = session.query(IO).filter_by(id=proto_door.io_id).first()
    if io is None:
        logging.error(f'Editing Door error: IO {proto_door.io_id} not found!')
        return

    door = session.query(Door).filter_by(id=proto_door.id).first()
    door.name = proto_door.name
    door.io_id = io.id
    door.in_pin = proto_door.in_pin
    door.out_pin = proto_door.out_pin
    door.delay = proto_door.delay
    door.nonc = proto_door.nonc

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Editing Door error: {}'.format(e))
        rs = False

    notify_mons_door_created(door, rs)


def remove_door(id):
    session = Session()
    session.query(Door).filter_by(id=id).delete()

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Deleting IO error: {}'.format(e))
        rs = False

    notify_mons_door_removed(id, rs)

    return True


def update_service_dvrs_assoc(svc_id, dvr_ids):
    session = Session()
    svc = session.query(Service).filter_by(id=svc_id).first()
    if svc is None:
        logging.error(f'Update svc-dvr assoc error: SVC not found {svc_id}')
        return

    svc.dvrs = []

    for dvr_id in dvr_ids:
        dvr = session.query(DVR).filter_by(id=dvr_id).first()
        if dvr is None:
            logging.error(f'Assoc error: DVR not found {dvr_id}')
        else:
            svc.dvrs.append(dvr)

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Updating assoc error: {}'.format(e))
        rs = False

    notify_mons_assoc_created(svc_id, dvr_ids, rs)


def remove_service_dvrs_assoc(service_id):
    session = Session()
    svc = session.query(Service).filter_by(id=service_id).first()
    svc.dvrs = []

    try:
        session.commit()
        return True
    except Exception as e:
        logging.error(f'Deleting assoc error {service_id}')
        return False


def remove_streams_by_dvr(dvr_id):
    session = Session()
    query = session.query(Stream).filter_by(dvr_id=dvr_id)
    ids = [s.id for s in query.all()]

    query.delete()
    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Removing all streams in dvr error: {}'.format(e))
        rs = False

    for id in ids:
        notify_mons_stream_removed(id, rs)


def remove_ios_by_dvr(dvr_id):
    session = Session()
    query = session.query(IO).filter_by(dvr_id=dvr_id)
    ios = query.all()
    ids = [io.id for io in ios]
    for io in ios:
        remove_doors_by_io(io.id)

    query.delete()

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Removing ios by dvr error: {}'.format(e))
        rs = False

    for id in ids:
        notify_mons_io_removed(id, rs)


def remove_doors_by_io(io_id):
    session = Session()
    query = session.query(Door).filter_by(io_id=io_id)
    ids = [d.id for d in query.all()]
    query.delete()

    rs = True
    try:
        session.commit()
    except Exception as e:
        logging.error('Removing doors by io error: {}'.format(e))
        rs = False

    for d in ids:
        notify_mons_door_removed(d, rs)
