import logging
import hashlib
import os
import uuid
import copy
import time
import json
import datetime
from queue import Queue


#logging.basicConfig(level=logging.DEBUG)
LOGGER = logging.getLogger('sampacs')

commitment_queue = Queue()

from pydicom.dataset import Dataset
from pydicom.dataelem import DataElement
from pydicom.filereader import dcmread
from pynetdicom import AE, StoragePresentationContexts
from pydicom.sequence import Sequence
from pydicom.uid import UID
import pynetdicom.sop_class as sop_class
from transliterate import translit
import base64
import re
aetitlepattern = re.compile('[\W]+')

from config import config
from dbmodel import *
from utils import saveFile, encodingTag, prepareDataset, formatShortName, formatFullName, getImage, worklist_search_query, toImage, get_redis_client
from PIL import Image


def handle_c_echo(event):
    return 0x0000

def handle_store(event):
    try:
        aetitle = event.assoc.remote['ae_title'].strip()
        aetitle = aetitlepattern.sub('', aetitle)
    except:
        aetitle = 'no aetitle'
    msg = 'STORE    request from {} ({})'.format(aetitle, event.assoc.remote['address'])
    LOGGER.info(msg)
    LOGGER.debug('base64 aetitle %s'%base64.b64encode(aetitle.encode()))
    ds = event.dataset
    #LOGGER.debug(ds)

    # Check for group 0002 elements in main dataset (should only be in file_meta)
    group_0002_in_ds = ds.group_dataset(0x0002)
    if len(group_0002_in_ds) > 0:
        LOGGER.warning(f'STORE    Found {len(group_0002_in_ds)} group 0002 elements in main dataset from {aetitle}')
        for tag, elem in group_0002_in_ds.items():
            # Handle both DataElement and RawDataElement
            elem_name = elem.name if hasattr(elem, 'name') else 'Unknown'
            elem_type = type(elem).__name__
            LOGGER.warning(f'STORE    Removing ({tag.group:04x},{tag.element:04x}) {elem_name} [{elem_type}]: {elem.value} from main dataset')
            del ds[tag]

    if 'StudyInstanceUID' not in ds:
        LOGGER.error('STORE    no StudyInstanceUID provided')
        return 0x0117
        #return 0x0000
    curModality = None
    if aetitle:
        for modality in config.modalities:
            if modality.aetitle == aetitle:
                curModality = modality
                if modality.encoding:
                    ds.set_original_encoding(event.context.transfer_syntax.is_implicit_VR, event.context.transfer_syntax.is_little_endian, modality.encoding)
                    ds.add(encodingTag)
                break
    if curModality:
        LOGGER.debug('STORE    modality %s found'%curModality.name)
    else:
        LOGGER.debug('STORE    modality not found')
    if 'StudyDate' not in ds or ds.StudyDate == '':
        if curModality and curModality.fillEmptyStudyDate:
            ds.StudyDate = datetime.datetime.now().strftime("%Y%m%d")
        else:
            LOGGER.error('STORE    no StudyDate provided')
            return 0x0117

    ds.file_meta = event.file_meta
    try:
        (ds, error) = prepareDataset(ds, registrateOptions = config.registrateOptions, modality = curModality)
        saveFile(ds, aetitle, True)
    except Exception as e:
        LOGGER.error(e)
        return 0xC211
    msg = 'STORE    study uid {}'.format(ds.StudyInstanceUID)
    LOGGER.info(msg)

    autorouteList = config.autoroute + (curModality.autoroute if curModality else [])
    for destName in autorouteList:
        addImageForSend(destName, ds.SOPInstanceUID)

    return 0x0000


def check_temp_session(aetitle):
    """
    Check if there's an active temp session for the given AETitle.

    Returns:
        Session data dict if active session exists, None otherwise
    """
    try:
        redis_client = get_redis_client()
        session_keys = redis_client.keys("temp_capture:*")

        for key in session_keys:
            session_json = redis_client.get(key)
            if session_json:
                session_data = json.loads(session_json)
                if (session_data["equipment_aet"] == aetitle and
                    session_data.get("active") == True):
                    return session_data

        return None
    except Exception as e:
        LOGGER.error(f"Error checking temp sessions: {e}")
        return None


def handle_store_wrapper(event):
    """
    Wrapper for DICOM store that checks for temp sessions first.
    """
    try:
        aetitle = event.assoc.remote['ae_title'].strip()
        aetitle = aetitlepattern.sub('', aetitle)
    except:
        aetitle = 'no aetitle'

    # Check for active temp session
    session_data = check_temp_session(aetitle)
    if session_data:
        LOGGER.info(f"Using temp store for session {session_data['session_id']}")
        return handle_temp_store(event, session_data)
    else:
        # Use normal store
        return handle_store(event)


def handle_temp_store(event, session_data):
    """
    Handle DICOM store for temporary capture session.

    Args:
        event: DICOM store event
        session_data: Temporary capture session data from Redis

    Returns:
        Storage status code
    """
    try:
        aetitle = event.assoc.remote['ae_title'].strip()
        aetitle = aetitlepattern.sub('', aetitle)
    except:
        aetitle = 'no aetitle'

    msg = 'TEMPSTORE request from {} ({}) for session {}'.format(
        aetitle, event.assoc.remote['address'], session_data['session_id']
    )
    LOGGER.info(msg)

    # Log incoming encoding for debugging
    print(event.dataset.SpecificCharacterSet)
    ds = event.dataset
    print(ds.SpecificCharacterSet)

    # Check for group 0002 elements in main dataset (should only be in file_meta)
    group_0002_in_ds = ds.group_dataset(0x0002)
    if len(group_0002_in_ds) > 0:
        LOGGER.warning(f'TEMPSTORE Found {len(group_0002_in_ds)} group 0002 elements in main dataset from {aetitle}')
        for tag, elem in group_0002_in_ds.items():
            # Handle both DataElement and RawDataElement
            elem_name = elem.name if hasattr(elem, 'name') else 'Unknown'
            elem_type = type(elem).__name__
            LOGGER.warning(f'TEMPSTORE Removing ({tag.group:04x},{tag.element:04x}) {elem_name} [{elem_type}]: {elem.value} from main dataset')
            del ds[tag]

    if 'StudyInstanceUID' not in ds:
        LOGGER.error('TEMPSTORE no StudyInstanceUID provided')
        return 0x0117

    # Get modality config
    curModality = None
    if aetitle:
        for modality in config.modalities:
            if modality.aetitle == aetitle:
                curModality = modality
                if modality.encoding:
                    ds.set_original_encoding(event.context.transfer_syntax.is_implicit_VR,
                                           event.context.transfer_syntax.is_little_endian,
                                           modality.encoding)
                    ds.add(encodingTag)
                break

    ds.file_meta = event.file_meta

    # Save to temp folder
    session_id = session_data['session_id']
    temp_folder = session_data['temp_folder']

    # Create study and series directories
    study_uid = ds.StudyInstanceUID
    series_uid = ds.SeriesInstanceUID
    sop_instance_uid = ds.SOPInstanceUID

    study_dir = os.path.join(temp_folder, 'dicom', study_uid)
    series_dir = os.path.join(study_dir, series_uid)
    os.makedirs(series_dir, exist_ok=True)

    # Save DICOM file
    dicom_path = os.path.join(series_dir, f'{sop_instance_uid}.dcm')
    try:
        ds.save_as(dicom_path, write_like_original=False)
    except Exception as e:
        LOGGER.error(f'TEMPSTORE save_as error: {e}')
        ds.save_as(dicom_path, write_like_original=True)

    LOGGER.info(f'TEMPSTORE saved to {dicom_path}')

    # Generate preview for first instance of each series
    preview_dir = os.path.join(temp_folder, 'preview')
    os.makedirs(preview_dir, exist_ok=True)
    preview_path = os.path.join(preview_dir, f'{series_uid}.jpg')

    if not os.path.exists(preview_path) and ds.Modality not in ['SR'] and 'PixelData' in ds and ds.PixelData:
        try:
            image = toImage(ds, 0)
            if image:
                image.save(preview_path, format='JPEG', quality=50)
                LOGGER.info(f'TEMPSTORE generated preview: {preview_path}')
        except Exception as e:
            LOGGER.error(f'TEMPSTORE preview generation error: {e}')

    # Send event to Redis Stream for every DICOM file
    try:
        redis_client = get_redis_client()
        stream_key = f"temp_capture_events:{session_id}"

        # Count instances in this series
        instance_count = len([f for f in os.listdir(series_dir) if f.endswith('.dcm')])

        event_data = {
            'session_id': session_id,
            'study_uid': study_uid,
            'series_uid': series_uid,
            'series_description': getattr(ds, 'SeriesDescription', ''),
            'modality': getattr(ds, 'Modality', ''),
            'preview_url': f'/images/tmp/{session_id}/preview/{series_uid}.jpg',
            'instance_count': instance_count,
            'timestamp': datetime.datetime.now().isoformat()
        }

        redis_client.xadd(stream_key, event_data)
        LOGGER.debug(f'TEMPSTORE sent event to Redis: {event_data}')

    except Exception as e:
        LOGGER.error(f'TEMPSTORE Redis error: {e}')

    return 0x0000

def addImageForSend(destName, instanceuid):
    try:
        pacs_db.connect()
        l = SendLog()
        l.instance_uid = instanceuid
        l.destination = destName
        l.save()
    except Exception as e:
        LOGGER.error('ROUTE    Routing failed with "%s"' % str(e))
        status = None
    finally:
        pacs_db.close()


def handle_open(event):
    msg = 'New connection   from {}'.format(event.assoc.remote['address'])
    LOGGER.debug(msg)

def parseSearchDate(d):
    if '-' in d:
        d = d.split('-')
        f = t = 1
        if d[0]:
            f = (Study.date >= datetime.datetime.strptime(d[0], "%Y%m%d"))
        if d[1]:
            t = (Study.date <= datetime.datetime.strptime(d[1], "%Y%m%d"))
        return (f & t)
    else:
        return (Study.date == datetime.datetime.strptime(d, "%Y%m%d"))

def parseSearchDateSql(field, d):
    if '-' in d:
        d = d.split('-')
        conditions = []
        params = []
        if d[0]:
            conditions.append(f'{field} >= DATE(%s) ')
            params.append(datetime.datetime.strptime(d[0], "%Y%m%d").isoformat())
        if d[1]:
            conditions.append(f'{field} <= DATE(%s) ')
            params.append(datetime.datetime.strptime(d[1], "%Y%m%d").isoformat())

        return (conditions, params)
    else:
        return ([f'{field} = DATE(%s) '], [datetime.datetime.strptime(d, "%Y%m%d").isoformat()])

def handle_find(event):
    def prepareField(val, encoding, needTranslit, translitLang = 'ru', uppercase = False):
        if needTranslit:
            val = translit(val, translitLang, reversed=True)[:64]
        if uppercase:
            val = val.upper()
        if encoding:
            val = val.encode(encoding)[:64]
        return val[:64]

    try:
        aetitle = event.assoc.remote['ae_title'].strip()
        aetitle = aetitlepattern.sub('', aetitle)
    except:
        LOGGER.error('no aetitle found in request!')
        aetitle = 'no aetitle'
    ds = event.identifier
    searchTags = {'PatientID': lambda t: Patient.id % t.replace('*','%'),
                            'PatientName': lambda t: Patient.name % str(t).replace('*','%'),
                            'PatientBirthDate': lambda t: Patient.birthday == datetime.datetime.strptime(t, "%Y%m%d"),
                            'StudyDate': lambda t: parseSearchDate(t),
                            'StudyInstanceUID': lambda t: Study.uid == t,
                            'ModalitiesInStudy': lambda t: Series.modality << list(t),
                            }

    fillTags =       {'PatientID': lambda series: series.study.patient.id,
                            'PatientName': lambda series: series.study.patient.name,
                            'PatientBirthDate': lambda series: series.study.patient.birthday.strftime("%Y%m%d") if series.study.patient.birthday else None,
                            'PatientSex': lambda series: series.study.patient.sex,
                            'StudyDate': lambda series: series.study.date.strftime("%Y%m%d"),
                            'StudyTime': lambda series: series.study.time,
                            'StudyDescription': lambda series: series.study.description,
                            'StudyInstanceUID': lambda series: series.study.uid,
                            'SeriesDescription': lambda series: series.description,
                            #'SOPClassUID': lambda series: series.description,
                            'SeriesDate': lambda series: series.date.strftime("%Y%m%d") if series.date else None,
                            'Modality': lambda series: series.modality,
                            'SeriesInstanceUID': lambda series: series.uid,
                            'AccessionNumber': lambda series: series.study.accession_number
                            }

    if event.context.abstract_syntax == sop_class.ModalityWorklistInformationFind: # pyright: ignore
        msg = 'WORKLIST request from {} ({})'.format(aetitle, event.assoc.remote['address'])
        LOGGER.info(msg)
        encoding = None
        needTranslit = None
        translitLang = 'ru'
        uppercase = False
        ignoreActionName = False
        generateStudyUID = None
        additionalTags = []
        wlFullName = False
        wlShortName2 = False
        modalityTag = ''
        createStepID = False
        if aetitle:
            for modality in config.modalities:
                if modality.aetitle == aetitle:
                    encoding = modality.wlEncoding
                    needTranslit = modality.translit
                    translitLang = modality.translitLang
                    uppercase = modality.uppercase
                    ignoreActionName = modality.ignoreActionName
                    generateStudyUID = modality.generateStudyUID
                    additionalTags = modality.additionalTags
                    wlFullName = modality.wlFullName
                    wlShortName2 = modality.wlShortName2
                    modalityTag = modality.modalityTag
                    if modality.wlEncoding:
                        ds.set_original_encoding(event.context.transfer_syntax.is_implicit_VR,
                                                 event.context.transfer_syntax.is_little_endian, modality.wlEncoding)

        searchTags = {'PatientID': lambda t: (['c.id = %s'], [t.replace('*','%')]),
                      'PatientName': lambda t: (['c.lastName like %s'], [t.family_name.replace("*","%%")]),
                      'PatientBirthDate': lambda t: parseSearchDateSql("c.birthDate", t),
                      'ScheduledProcedureStepStartDate': lambda t: parseSearchDateSql("DATE(Job_Ticket.dateTime)", t)
                     }

        fillTags =       {'PatientID': lambda d: str(d['clientId']),
                            'PatientName': lambda d: prepareField(d['patientName'], encoding, needTranslit, translitLang, uppercase),
                            'PatientBirthDate': lambda d: d['birthDate'].strftime("%Y%m%d"),
                            'PatientSex': lambda d: d['sex'],
                            'AccessionNumber': lambda d: str(d['actionId']),
                            'RequestedProcedureDescription': lambda d: "" if ignoreActionName else prepareField(d['actionName'], encoding, needTranslit, translitLang, False),
                            'StudyDescription': lambda d: "" if ignoreActionName else prepareField(d['actionName'], encoding, needTranslit, translitLang, False)
                            }
        sheduledProcSequenceTags = { 'ScheduledProcedureStepStartDate': lambda d: d['ticketDate'].strftime("%Y%m%d") if d['ticketDate'] else "",
                                     'ScheduledProcedureStepStartTime': lambda d: d['ticketDate'].strftime("%H%M") if d['ticketDate'] else "",
                                     #'ScheduledProcedureStepLocation': lambda d: 'location',
                                     'ScheduledProcedureStepDescription': lambda d: "" if ignoreActionName else prepareField(d['actionName'], encoding, needTranslit, translitLang, False),
                                     'ScheduledStationAETitle': lambda d: d['aet'],
                                     'ScheduledProcedureStepID': lambda d: str(d['actionId'])
                                     #'ScheduledStationName': lambda d: d['AET']
                                    }

        cond = ['eq.labCode = %s']
        params = [aetitle]
        for t in ds.iterall():
            if t.keyword in searchTags and t.value:
                try:
                    c,p = searchTags[t.keyword](t.value)
                    cond.extend(c)
                    params.extend(p)
                except Exception as e:
                    LOGGER.error("WORKLIST ERROR, keyword '%s', value '%s': %s"%(t.keyword, t.value, str(e)))
                    if t.keyword == "ScheduledProcedureStepStartDate":
                        yield 0xC000, None
                        return
        LOGGER.info('WORKLIST search cond: %s'%', '.join(cond))
        query_results = worklist_search_query(cond, params, wlFullName)
        resultList = []
        for data in query_results:
            result = Dataset(copy.deepcopy(ds._dict))
            if not encoding:
                result.SpecificCharacterSet = 'ISO_IR 192'
            elif encoding.upper() == 'ISO-8859-5':
                result.SpecificCharacterSet = 'ISO_IR 144'
            if result.ScheduledProcedureStepSequence:
                for seq in result.ScheduledProcedureStepSequence:
                    if modalityTag:
                        seq.Modality = modalityTag
                        seq.ScheduledProcedureStepID = '1'
                    for key in sheduledProcSequenceTags.keys():
                        if key in seq:
                            tag = seq[key]
                            tag.value = sheduledProcSequenceTags[key](data)
            for tag in additionalTags:
                if type(tag) is not dict:
                    LOGGER.error('WORKLIST Tag must be in dict format {tag: nn, vr: nn, value: nn}')
                    continue
                if 'tag' not in tag or 'value' not in tag or 'vr' not in tag:
                    LOGGER.error('WORKLIST Tag must be in dict format {tag: nn, vr: nn, value: nn}')
                    continue
                try:
                    newTag = DataElement(tag['tag'], tag['vr'], str(tag['value']))
                    result.add(newTag)
                except Exception as e:
                    LOGGER.error(str(e))

            aBegDate = data.get('actionBegDate')
            actionId = data['actionId']
            if generateStudyUID and aBegDate:
                result.StudyInstanceUID = '1.2.840.113663.1500.1.422763892.1.1.%s.180631.%s'%(aBegDate.strftime("%Y%m%d"), str(actionId)) #str(uuid.uuid1()) # '1.2.840.113663.1500.1.422763892.1.1.20201006.180631.'+str(actionId)
            for key in fillTags.keys():
                if key in result:
                    tag = result[key]
                    tag.value = fillTags[key](data)
                    result.add(tag)
            resultList.append(result)
        LOGGER.info(f'WORKLIST found {len(resultList)}')
        for i, result in enumerate(resultList):
                yield (0xFF00, result)
        return

    msg = 'FIND     request from {} ({})'.format(aetitle, event.assoc.remote['address'])
    LOGGER.info(msg)
    if 'QueryRetrieveLevel' not in ds:
        # Failure
        yield 0xC000, None
        return
    #pacs_db.connect()
    if ds.QueryRetrieveLevel == 'IMAGE':
        if 'SeriesInstanceUID' not in ds:
            yield 0xC000, None
            return
        pacs_db.connect()
        imageQuery = Instance.select().where(Instance.series_id == ds.SeriesInstanceUID)
        imageQuery.execute()
        pacs_db.close()
        for image in imageQuery:
            if event.is_cancelled:
                yield (0xFE00, None)
                return
            result = Dataset(ds)
            result.TransferSyntaxUID = image.transfer_syntax
            result.SopClass = image.sopclass
            result.SOPInstanceUID = image.uid
            result.InstanceNumber = image.number
            yield (0xFF00, result)
        return

    pacs_db.connect()
    seriesQuery = Series.select(Series, Study, Patient).join(Study).join(Patient)
    for key in searchTags.keys():
        if key in ds and ds.get(key) != '':
            seriesQuery = seriesQuery.where(searchTags[key](ds.get(key)))

    if ds.QueryRetrieveLevel == 'PATIENT':
        seriesQuery = seriesQuery.group_by(Patient.id)

    if ds.QueryRetrieveLevel == 'STUDY':
        seriesQuery = seriesQuery.group_by(Study.uid)

    seriesQuery.execute()


    for series in seriesQuery:
    # Check if C-CANCEL has been received
        if event.is_cancelled:
            yield (0xFE00, None)
            return

        result = Dataset(ds)
        result.add(encodingTag)
        if 'ModalitiesInStudy' in ds:
            query = Series.select(Series.modality).where(Series.study_id == series.study).distinct()
            ds.ModalitiesInStudy = []
            for s in query:
                ds.ModalitiesInStudy.append(s.modality)
        if 'NumberOfStudyRelatedInstances' in ds:
            n = Instance.select(fn.COUNT('*')).join(Series).where(Series.study_id == series.study).scalar()
            ds.NumberOfStudyRelatedInstances = n
        for key in fillTags.keys():
            if key in result:
                tag = result[key]
                tag.value = fillTags[key](series)
                result.add(tag)

        yield (0xFF00, result)
    pacs_db.close()


def handle_get(event):
    """Handle a C-GET request event."""
    ds = event.identifier
    if 'QueryRetrieveLevel' not in ds:
        # Failure
        yield 0xC000, None
        return

    pacs_db.connect()
    instanceQuery = Instance.select(Instance.uid, Instance.study_date, Series.uid, Study.uid, Study.nodeCode, Patient.id).join(Series).join(Study).join(Patient)
    if ds.QueryRetrieveLevel == 'PATIENT':
        if 'PatientID' not in ds:
            yield 0xC000, None
            pacs_db.close()
            return
        instanceQuery = instanceQuery.where(Patient.id == ds.PatientID)
    if ds.QueryRetrieveLevel == 'STUDY':
        if 'StudyInstanceUID' not in ds:
            yield 0xC000, None
            pacs_db.close()
            return
        instanceQuery = instanceQuery.where(Study.uid == ds.StudyInstanceUID)
    if ds.QueryRetrieveLevel == 'SERIES':
        if 'SeriesInstanceUID' not in ds:
            yield 0xC000, None
            pacs_db.close()
            return
        instanceQuery = instanceQuery.where(Series.uid == ds.SeriesInstanceUID)
    if ds.QueryRetrieveLevel == 'IMAGE':
        if 'SOPInstanceUID' not in ds:
            yield 0xC000, None
            pacs_db.close()
            return
        instanceQuery = instanceQuery.where(Instance.uid == ds.SOPInstanceUID)
    instanceQuery.execute()
    pacs_db.close()



    yield len(instanceQuery)

    # Yield the matching instances
    for instance in instanceQuery:
        # Check if C-CANCEL has been received
        if event.is_cancelled:
            yield (0xFE00, None)
            return

        #filename = instance.uid + '.dcm'
        #uidhash = hashlib.md5(instance.uid.encode('utf8')).hexdigest()[:2]
        #studyMonth = instance.study_date.strftime("%Y%m")
        #dir = os.path.join(config.storageDir, studyMonth, uidhash)
        #res = dcmread(os.path.join(dir, filename))
        res = getImage(instance.uid, instance.study_date.strftime("%Y%m"), instance.series.study.nodeCode)
        yield (0xFF00, res)


def handle_move(event):
    try:
        aetitle = event.assoc.remote['ae_title'].strip()
    except:
        aetitle = 'no aetitle'
    msg = 'MOVE     request from {} ({})'.format(aetitle, event.assoc.remote['address'])
    LOGGER.info(msg)
    ds = event.identifier
    if 'QueryRetrieveLevel' not in ds:
        # Failure
        yield 0xC000, None
        return

    moveAetitle = event.move_destination.strip()
    moveModality = None
    for modality in config.modalities:
        if modality.aetitle == moveAetitle:
            moveModality = modality
            break
    if not moveModality:
        yield 0xC000, None
        return

    pacs_db.connect()
    instanceQuery = Instance.select(Instance.uid, Instance.study_date, Series.uid, Study.uid, Study.nodeCode, Patient.id).join(Series).join(Study).join(Patient)
    if ds.QueryRetrieveLevel == 'PATIENT':
        if 'PatientID' not in ds:
            yield 0xC000, None
            pacs_db.close()
            return
        instanceQuery = instanceQuery.where(Patient.id == ds.PatientID)
    if ds.QueryRetrieveLevel == 'STUDY':
        if 'StudyInstanceUID' not in ds:
            yield 0xC000, None
            pacs_db.close()
            return
        instanceQuery = instanceQuery.where(Study.uid == ds.StudyInstanceUID)
    if ds.QueryRetrieveLevel == 'SERIES':
        if 'SeriesInstanceUID' not in ds:
            yield 0xC000, None
            pacs_db.close()
            return
        instanceQuery = instanceQuery.where(Series.uid == ds.SeriesInstanceUID)
    if ds.QueryRetrieveLevel == 'IMAGE':
        if 'SOPInstanceUID' not in ds:
            yield 0xC000, None
            pacs_db.close()
            return
        instanceQuery = instanceQuery.where(Instance.uid == ds.SOPInstanceUID)
    instanceQuery.execute()
    pacs_db.close()


    yield (modality.host, modality.port)
    yield len(instanceQuery)

    # Yield the matching instances
    for instance in instanceQuery:
        # Check if C-CANCEL has been received
        if event.is_cancelled:
            yield (0xFE00, None)
            return

        res = getImage(instance.uid, instance.study_date.strftime("%Y%m"), instance.series.study.nodeCode)
        yield (0xFF00, res)
    yield (0x0000, None)



def handle_n_action(event):
    if event.context.abstract_syntax == sop_class.StorageCommitmentPushModel:
        commitment_queue.put(event)
        return (0x0000, None)
    else:
        return (0x0211, None)

def thread_storage_commitment(q):
    while True:
        event = q.get()
        aetitle = event.assoc.remote['ae_title'].strip()
        msg = 'ST_COMM  request from {} ({})'.format(aetitle, event.assoc.remote['address'])
        LOGGER.info(msg)
        LOGGER.debug(f"thread_n_event  action_information: {event.action_information}")
        LOGGER.debug(f"assoc.is_established: {event.assoc.is_established}")

        refd_sop_sequence=event.action_information.ReferencedSOPSequence
        ReferencedSOPSequence=Sequence()
        FailedSOPSequence=Sequence()
        pacs_db.connect()
        for i in range(len(refd_sop_sequence)):
            LOGGER.debug(f"ReferencedSOPClassUID    : {refd_sop_sequence[i].ReferencedSOPClassUID}")
            LOGGER.debug(f"ReferencedSOPInstanceUID : {refd_sop_sequence[i].ReferencedSOPInstanceUID}")
            instance_uid = refd_sop_sequence[i].ReferencedSOPInstanceUID
            refd_sop = Dataset()
            refd_sop.ReferencedSOPClassUID = refd_sop_sequence[i].ReferencedSOPClassUID
            refd_sop.ReferencedSOPInstanceUID = instance_uid

            #instance = Instance.get_or_none(Instance.uid == instance_uid)
            try:
                instance = Instance.select(Instance, Study.nodeCode).join(Series).join(Study).where(Instance.uid == instance_uid).get()
            except:
                instance = None
            if instance:
                try:
                    #filename = instance_uid + '.dcm'
                    #uidhash = hashlib.md5(instance_uid.encode('utf8')).hexdigest()[:2]
                    #studyMonth = instance.study_date.strftime("%Y%m")
                    #dir = os.path.join(config.storageDir, studyMonth, uidhash)
                    #res = dcmread(os.path.join(dir, filename))
                    res = getImage(instance_uid, instance.study_date.strftime("%Y%m"), instance.series.study.nodeCode)
                    if res.SOPInstanceUID == instance_uid:
                        ReferencedSOPSequence.append(refd_sop)
                        LOGGER.info('ST_COMM  %s is commited'%instance_uid)
                    else:
                        LOGGER.error('ST_COMM  ERROR! SOPInstanceUID missmatch in database and file %s'%os.path.join(dir, filename))
                        FailedSOPSequence.append(refd_sop)
                except Exception as e:
                    LOGGER.error(str(e))
                    FailedSOPSequence.append(refd_sop)
            else:
                refd_sop.FailureReason = 274 #no such instance
                LOGGER.warning('ST_COMM  no instance found in db')
                FailedSOPSequence.append(refd_sop)

        pacs_db.close()
        ds = Dataset()
        ds.TransactionUID = event.action_information.TransactionUID
        ds.ReferencedSOPSequence = ReferencedSOPSequence
        ds.FailedSOPSequence = FailedSOPSequence

        ds.is_implicit_VR = True
        ds.is_little_endian = True

        response = None
        if event.assoc.is_established:
            response = event.assoc.send_n_event_report(dataset=ds, event_type=1, \
                                class_uid=sop_class.StorageCommitmentPushModel, \
                                instance_uid=UID('1.2.840.10008.1.20.1.1'))
            LOGGER.info('ST_COMM  successfully sended to {} ({})'.format(aetitle, event.assoc.remote['address']))
        else:
            LOGGER.info('ST_COMM  association not established, trying open new one to {} ({})'.format(aetitle, event.assoc.remote['address']))
            modalityFound = False
            for modality in config.modalities:
                modalityFound = True
                if modality.aetitle == aetitle:
                    ae = AE(ae_title=config.pacs_aetitle)
                    ae.add_requested_context(sop_class.StorageCommitmentPushModel)
                    assoc = ae.associate(modality.host, modality.port, ae_title = modality.aetitle)
                    if assoc.is_established:
                        response = assoc.send_n_event_report(dataset=ds, event_type=1, \
                                class_uid=sop_class.StorageCommitmentPushModel, \
                                instance_uid=UID('1.2.840.10008.1.20.1.1'))
                        LOGGER.info('ST_COMM  successfully sended to {} ({})'.format(aetitle, modality.host))
                    else:
                        LOGGER.error('ST_COMM  association with %s failed'%aetitle)
                    break
            if not modalityFound:
                LOGGER.error('ST_COMM  aetitle %s not found in config!'%aetitle)
        if response:
            LOGGER.debug('response')
            LOGGER.debug(f"{response}")


managed_instances = {}

def handle_n_create(event):
    LOGGER.info('CREATE   create request (mpps)')
    # MPPS' N-CREATE request must have an *Affected SOP Instance UID*
    req = event.request
    if req.AffectedSOPInstanceUID is None:
        LOGGER.error('CREATE   req.AffectedSOPInstanceUID is None')
        # Failed - invalid attribute value
        return 0x0106, None

    # Can't create a duplicate SOP Instance
    if req.AffectedSOPInstanceUID in managed_instances:
        # Failed - duplicate SOP Instance
        LOGGER.error('CREATE   duplicate SOP Instance')
        return 0x0111, None

    # The N-CREATE request's *Attribute List* dataset
    attr_list = event.attribute_list

    # Performed Procedure Step Status must be 'IN PROGRESS'
    if "PerformedProcedureStepStatus" not in attr_list:
        # Failed - missing attribute
        LOGGER.error('CREATE   missing attribute PerformedProcedureStepStatus')
        return 0x0120, None
    if attr_list.PerformedProcedureStepStatus.upper() != 'IN PROGRESS':
        LOGGER.error(f'CREATE   Status ({attr_list.PerformedProcedureStepStatus.upper()}) != IN PROGRESS')
        return 0x0106, None

    # Skip other tests...

    # Create a Modality Performed Procedure Step SOP Class Instance
    #   DICOM Standard, Part 3, Annex B.17
    ds = Dataset()

    # Add the SOP Common module elements (Annex C.12.1)
    ds.SOPClassUID = sop_class.ModalityPerformedProcedureStep
    ds.SOPInstanceUID = req.AffectedSOPInstanceUID

    # Update with the requested attributes
    ds.update(attr_list)

    # Add the dataset to the managed SOP Instances
    managed_instances[ds.SOPInstanceUID] = ds

    # Return status, dataset
    return 0x0000, ds

# Implement the evt.EVT_N_SET handler
def handle_n_set(event):
    LOGGER.info('SET      create request (mpps)')
    req = event.request
    if req.RequestedSOPInstanceUID not in managed_instances:
        # Failure - SOP Instance not recognised
        LOGGER.info('SET      SOP Instance not recognised')
        return 0x0112, None

    ds = managed_instances[req.RequestedSOPInstanceUID]

    # The N-SET request's *Modification List* dataset
    mod_list = event.attribute_list

    # Skip other tests...

    ds.update(mod_list)

    # Return status, dataset
    return 0x0000, ds
