# -*- coding: utf-8 -*-
from copy import deepcopy
import logging
import datetime
import operator

from pprint import pprint as pp

import pymongo

from scout.parse.case import parse_case
from scout.build.case import build_case
from scout.parse.variant.ids import parse_document_id
from scout.utils.algorithms import ui_score

from scout.exceptions import IntegrityError, ConfigError

LOG = logging.getLogger(__name__)

class CaseHandler(object):
    """Part of the pymongo adapter that handles cases and institutes"""

    def get_similar_cases(self, case_obj):
        """Take a case obj and return a iterable with the most phenotypically similar cases

        Args:
            case_obj(models.Case)

        Returns:
            scores(list(tuple)): Returns a list of tuples like (case_id, score) with the most
                                 similar case first
        """
        scores = {}
        set_1 = set()
        if not case_obj.get('phenotype_terms'):
            LOG.warning("No phenotypes could be found for case %s", case_obj['_id'])
            return None
        # Add all ancestors of all terms
        for term in case_obj['phenotype_terms']:
            hpo_term = self.hpo_term(term['phenotype_id'])
            if not hpo_term:
                continue
            set_1 = set_1.union(set(hpo_term.get('all_ancestors',[])))
        # Need to control what cases to look for here
        # Fetch all cases with phenotypes
        for case in self.cases(phenotype_terms=True, owner=case_obj['owner']):
            set_2 = set()
            if case['_id'] == case_obj['_id']:
                continue
            # Add all ancestors if all terms
            for term in case['phenotype_terms']:
                hpo_term = self.hpo_term(term['phenotype_id'])
                if not hpo_term:
                    continue
                set_2 = set_2.union(set(hpo_term.get('all_ancestors',[])))
            LOG.debug("Check phenotypic similarity of %s and %s", case_obj['_id'], case['_id'])
            scores[case['_id']] = ui_score(set_1, set_2)
        # Returns a list of tuples with highest score first
        return sorted(scores.items(), key=operator.itemgetter(1), reverse=True)

    def cases(self, owner=None, collaborator=None, query=None, skip_assigned=False,
              has_causatives=False, reruns=False, finished=False,
              research_requested=False, is_research=False, status=None,
              phenotype_terms=False, pinned=False, cohort=False, name_query=None,
              yield_query=False):
        """Fetches all cases from the backend.

        Args:
            collaborator(str): If collaborator should be considered
            owner(str): Query cases for specified case owner only
            query(dict): If a specific query is used
            skip_assigned(bool)
            has_causatives(bool)
            reruns(bool)
            finished(bool)
            research_requested(bool)
            is_research(bool)
            status(str)
            phenotype_terms(bool): Fetch all cases with phenotype terms
            pinned(bool): Fetch all cases with pinned variants
            name_query(str): Could be hpo term, HPO-group, user, part of display name,
                             part of inds or part of synopsis
            yield_query(bool): If true, only return mongo query dict for use in
                                compound querying.

        Returns:
            Cases ordered by date.
            If yield_query is True, does not pose query to db;
                instead returns corresponding query dict
                that can be reused in compound queries or for testing.
        """
        LOG.debug("Fetch all cases")
        query = query or {}
        order = None

        # Prioritize when both owner and collaborator params are present
        if collaborator and owner:
            collaborator = None

        if collaborator:
            LOG.debug("Use collaborator {0}".format(collaborator))
            query['collaborators'] = collaborator

        if owner:
            LOG.debug("Use owner {0}".format(owner))
            query['owner'] = owner

        if skip_assigned:
            query['assignees'] = {'$exists': False}

        if has_causatives:
            query['causatives'] = {'$exists': True, '$ne': []}

        if reruns:
            query['rerun_requested'] = True

        if status:
            query['status'] = status

        elif finished:
            query['status'] = {'$in': ['solved', 'archived']}

        if research_requested:
            query['research_requested'] = True

        if is_research:
            query['is_research'] = {'$exists': True, '$eq': True}

        if phenotype_terms:
            query['phenotype_terms'] = {'$exists': True, '$ne': []}

        if pinned:
            query['suspects'] = {'$exists': True, '$ne': []}

        if cohort:
            query['cohorts'] = {'$exists': True, '$ne': []}

        if name_query:
            name_value = name_query.split(':')[-1] # capture ant value provided after query descriptor
            users = self.user_collection.find({'name': {'$regex': name_query, '$options': 'i'}})
            nr_users = sum(1 for i in self.user_collection.find({'name': {'$regex': name_query, '$options': 'i'}}))
            if nr_users > 0:
                query['assignees'] = {'$in': [user['email'] for user in users]}
            elif name_query.startswith('HP:'):
                LOG.debug("HPO case query")
                if name_value:
                    query['phenotype_terms.phenotype_id'] = name_query
                else: # query for cases with no HPO terms
                    query['$or'] = [ {'phenotype_terms' : {'$size' : 0}}, {'phenotype_terms' : {'$exists' : False}} ]
            elif name_query.startswith('PG:'):
                LOG.debug("PG case query")
                if name_value:
                    phenotype_group_query = name_query.replace('PG:', 'HP:')
                    query['phenotype_groups.phenotype_id'] = phenotype_group_query
                else: # query for cases with no phenotype groups
                    query['$or'] = [ {'phenotype_groups' : {'$size' : 0}}, {'phenotype_groups' : {'$exists' : False}} ]
            elif name_query.startswith('similar:'):
                LOG.debug("Case HPO similarity query")
                if name_value:
                    # first, see that we can find a unique case with the given display name to match against for owner
                    if owner:
                        search_institute_id=owner
                    elif collaborator:
                        search_institute_id=collaborator
                    else:
                        raise ValueError("No owner or collaborator institute_id given.")
                    case_obj = self.case(display_name=name_value, institute_id=search_institute_id)
                    if case_obj:
                        LOG.debug("Search for cases similar to %s", case_obj.get('display_name'))
                        similar_cases = self.get_similar_cases(case_obj)
                        LOG.debug("Similar cases: %s",similar_cases)
                        if similar_cases:
                            similar_case_ids = []
                            order = []
                            for i in similar_cases:
                                similar_case_ids.append(i[0])
                                order.append(i[1])
                            query['_id'] = {'$in': similar_case_ids}
            elif name_query.startswith('causative:'):
                if name_value:
                    hgnc_id=self.hgnc_id(hgnc_symbol=name_value)
                    if hgnc_id:
                        cases_with_gene_doc=self.case_collection.aggregate([
                            { '$unwind': "$causatives" },
                            { '$lookup':
                                {'from': 'variant',
                                'localField': 'causatives',
                                'foreignField': '_id',
                                'as': 'causative_variant'}
                            },
                            {'$match': {
                                'causative_variant.hgnc_ids': hgnc_id
                            }},
                            {'$project': {'_id': 1}}])
                        case_ids = [case['_id'] for case in cases_with_gene_doc]
                        query['_id'] = {'$in': case_ids}
                    else:
                        LOG.info("No gene with the HGNC symbol {} found.".format(name_value))
            elif name_query.startswith('pinned:'):
                if name_value:
                    hgnc_id=self.hgnc_id(hgnc_symbol=name_value)
                    if hgnc_id:
                        cases_with_gene_doc=self.case_collection.aggregate([
                            { '$unwind': "$suspects"},
                            { '$lookup':
                                {'from': 'variant',
                                'localField': 'suspects',
                                'foreignField': '_id',
                                'as': 'suspect_variant'}
                            },
                            {'$match': {
                                'suspect_variant.hgnc_ids': hgnc_id
                            }},
                            {'$project': {'_id': 1}}])
                        case_ids = [case['_id'] for case in cases_with_gene_doc]
                        query['_id'] = {'$in': case_ids}
                    else:
                        LOG.info("No gene with the HGNC symbol {} found.".format(name_value))
            elif name_query.startswith('synopsis:'):
                if name_value:
                    query['$text']={'$search':name_value}
                else: # query for cases with missing synopsis
                    query['synopsis'] = ''
            elif name_query.startswith('cohort:'):
                query['cohorts'] = name_value
            elif name_query.startswith('panel:'):
                query['panels'] = {'$elemMatch': {'panel_name': name_value,
                                    'is_default': True }}
            elif name_query.startswith('status:'):
                status_query = name_query.replace('status:','')
                query['status'] = status_query
            elif name_query.startswith('is_research'):
                query['is_research'] = {'$exists': True, '$eq': True}
            else:
                query['$or'] = [
                    {'display_name': {'$regex': name_query}},
                    {'individuals.display_name': {'$regex': name_query}},
                ]

        if yield_query:
            return query

        LOG.info("Get cases with query {0}".format(query))
        if(order):
            return self.case_collection.find(query)
        else:
            return self.case_collection.find(query).sort('updated_at', -1)

    def nr_cases(self, institute_id=None):
        """Return the number of cases

        This function will change when we migrate to 3.7.1

        Args:
            collaborator(str): Institute id

        Returns:
            nr_cases(int)
        """
        query = {}

        if institute_id:
            query['collaborators'] = institute_id

        LOG.debug("Fetch all cases with query {0}".format(query))
        nr_cases = sum(1 for i in self.case_collection.find(query))

        return nr_cases


    def update_dynamic_gene_list(self, case, hgnc_symbols=None, hgnc_ids=None,
                                 phenotype_ids=None, build='37', add_only=False):
        """Update the dynamic gene list for a case

        Adds a list of dictionaries to case['dynamic_gene_list'] that looks like

        {
            hgnc_symbol: str,
            hgnc_id: int,
            description: str
        }

        Arguments:
            case (dict): The case that should be updated
            hgnc_symbols (iterable): A list of hgnc_symbols
            hgnc_ids (iterable): A list of hgnc_ids

        Returns:
            updated_case(dict)
        """
        dynamic_gene_list = []
        if add_only:
            dynamic_gene_list  = self.case_collection.find_one({ '_id': case['_id'] },
                { 'dynamic_gene_list': 1, '_id': 0 })['dynamic_gene_list']
            LOG.debug("Add selected: current dynamic gene list: {}".format(dynamic_gene_list))

        res = []
        if hgnc_ids:
            LOG.info("Fetching genes by hgnc id: {}".format(hgnc_ids))
            res = self.hgnc_collection.find({'hgnc_id': {'$in': hgnc_ids}, 'build': build})
        elif hgnc_symbols:
            LOG.info("Fetching genes by hgnc symbols")
            for symbol in hgnc_symbols:
                those_genes = self.gene_by_alias(symbol=symbol, build=build)
                for gene_obj in those_genes:
                    res.append(gene_obj)

        for gene_obj in res:
            LOG.debug("Appending gene {}".format(gene_obj['hgnc_symbol']))
            dynamic_gene_list.append(
                {
                    'hgnc_symbol': gene_obj['hgnc_symbol'],
                    'hgnc_id': gene_obj['hgnc_id'],
                    'description': gene_obj['description'],
                }
            )

        LOG.info("Update dynamic gene panel for: %s", case['display_name'])
        updated_case = self.case_collection.find_one_and_update(
            {'_id': case['_id']},
            {'$set': {'dynamic_gene_list': dynamic_gene_list,
                      'dynamic_panel_phenotypes': phenotype_ids or []}},
            return_document=pymongo.ReturnDocument.AFTER
        )
        LOG.debug("Case updated")
        return updated_case

    def case(self, case_id=None, institute_id=None, display_name=None):
        """Fetches a single case from database

        Use either the _id or combination of institute_id and display_name

        Args:
            case_id(str): _id for a caes
            institute_id(str):
            display_name(str)

        Yields:
            A single Case
        """
        query = {}
        if case_id:
            query['_id'] = case_id
            LOG.info("Fetching case %s", case_id)
        else:
            if not (institute_id and display_name):
                raise ValueError("Have to provide both institute_id and display_name")
            LOG.info("Fetching case %s institute %s", display_name, institute_id)
            query['owner'] = institute_id
            query['display_name'] = display_name

        return self.case_collection.find_one(query)

    def case_ind(self, ind_id):
        """Fetch cases based on an individual id.

        Args:
            ind_id(str)

        Returns:
            cases(pymongo.cursor): The cases with a matching ind_id
        """

        return self.case_collection.find({'individuals.disply_name': ind_id})

    def delete_case(self, case_id=None, institute_id=None, display_name=None):
        """Delete a single case from database

        Args:
            institute_id(str)
            case_id(str)

        Returns:
            case_obj(dict): The case that was deleted
        """
        query = {}
        if case_id:
            query['_id'] = case_id
            LOG.info("Deleting case %s", case_id)
        else:
            if not (institute_id and display_name):
                raise ValueError("Have to provide both institute_id and display_name")
            LOG.info("Deleting case %s institute %s", display_name, institute_id)
            query['owner'] = institute_id
            query['display_name'] = display_name

        result = self.case_collection.delete_one(query)
        return result

    def load_case(self, config_data, update=False):
        """Load a case into the database

        Check if the owner and the institute exists.

        Args:
            config_data(dict): A dictionary with all the necessary information
            update(bool): If existing case should be updated

        Returns:
            case_obj(dict)
        """
        # Check that the owner exists in the database
        institute_obj = self.institute(config_data['owner'])
        if not institute_obj:
            raise IntegrityError("Institute '%s' does not exist in database" % config_data['owner'])

        # Parse the case information
        parsed_case = parse_case(config=config_data)
        # Build the case object
        case_obj = build_case(parsed_case, self)
        # Check if case exists with old case id
        old_caseid = '-'.join([case_obj['owner'], case_obj['display_name']])
        old_case = self.case(old_caseid)
        # This is to keep sanger order and validation status
        old_sanger_variants = self.case_sanger_variants(case_obj['_id'])

        if old_case:
            LOG.info("Update case id for existing case: %s -> %s", old_caseid, case_obj['_id'])
            self.update_caseid(old_case, case_obj['_id'])
            update = True

        # Check if case exists in database
        existing_case = self.case(case_obj['_id'])
        if existing_case and not update:
            raise IntegrityError("Case %s already exists in database" % case_obj['_id'])

        files = [
            {'file_name': 'vcf_snv', 'variant_type': 'clinical', 'category': 'snv'},
            {'file_name': 'vcf_sv', 'variant_type': 'clinical', 'category': 'sv'},
            {'file_name': 'vcf_cancer', 'variant_type': 'clinical', 'category': 'cancer'},
            {'file_name': 'vcf_str', 'variant_type': 'clinical', 'category': 'str'}
        ]

        try:
            for vcf_file in files:
                # Check if file exists
                if not case_obj['vcf_files'].get(vcf_file['file_name']):
                    LOG.debug("didn't find {}, skipping".format(vcf_file['file_name']))
                    continue

                variant_type = vcf_file['variant_type']
                category = vcf_file['category']
                if update:
                    self.delete_variants(
                        case_id=case_obj['_id'],
                        variant_type=variant_type,
                        category=category
                    )
                self.load_variants(
                    case_obj=case_obj,
                    variant_type=variant_type,
                    category=category,
                    rank_threshold=case_obj.get('rank_score_threshold', 5),
                )

        except (IntegrityError, ValueError, ConfigError, KeyError) as error:
            LOG.warning(error)

        if existing_case and update:
            self.update_case(case_obj)

            # update Sanger status for the new inserted variants
            self.update_case_sanger_variants(institute_obj,case_obj, old_sanger_variants)

        else:
            LOG.info('Loading case %s into database', case_obj['display_name'])
            self._add_case(case_obj)

        return case_obj


    def _add_case(self, case_obj):
        """Add a case to the database
           If the case already exists exception is raised

            Args:
                case_obj(Case)
        """
        if self.case(case_obj['_id']):
            raise IntegrityError("Case %s already exists in database" % case_obj['_id'])

        return self.case_collection.insert_one(case_obj)

    def update_case(self, case_obj):
        """Update a case in the database

        The following will be updated:
            - collaborators: If new collaborators these will be added to the old ones
            - analysis_date: Is updated to the new date
            - analyses: The new analysis date will be added to old runs
            - individuals: There could be new individuals
            - updated_at: When the case was updated in the database
            - rerun_requested: Is set to False since that is probably what happened
            - panels: The new gene panels are added
            - genome_build: If there is a new genome build
            - genome_version: - || -
            - rank_model_version: If there is a new rank model
            - sv_rank_model_version: If there is a new sv rank model
            - madeline_info: If there is a new pedigree
            - vcf_files: paths to the new files
            - has_svvariants: If there are new svvariants
            - has_strvariants: If there are new strvariants
            - multiqc: If there's an updated multiqc report location
            - mme_submission: If case was submitted to MatchMaker Exchange

            Args:
                case_obj(dict): The new case information

            Returns:
                updated_case(dict): The updated case information
        """
        # Todo: rename to match the intended purpose

        LOG.info("Updating case {0}".format(case_obj['_id']))
        old_case = self.case_collection.find_one(
            {'_id': case_obj['_id']}
        )

        # if case is updated and was archived or active - make it inactive
        updated_status = old_case.get('status')
        if old_case.get('status') in ['archived', 'active']:
            updated_status = 'inactive'

        updated_case = self.case_collection.find_one_and_update(
            {'_id': case_obj['_id']},
            {
                '$addToSet': {
                    'collaborators': {'$each': case_obj['collaborators']},
                    'analyses': {
                        'date': old_case['analysis_date'],
                        'delivery_report': old_case.get('delivery_report')
                    }
                },
                '$set': {
                    'analysis_date': case_obj['analysis_date'],
                    'delivery_report': case_obj.get('delivery_report'),
                    'individuals': case_obj['individuals'],
                    'updated_at': datetime.datetime.now(),
                    'rerun_requested': False,
                    'panels': case_obj.get('panels', []),
                    'genome_build': case_obj.get('genome_build', '37'),
                    'genome_version': case_obj.get('genome_version'),
                    'rank_model_version': case_obj.get('rank_model_version'),
                    'sv_rank_model_version': case_obj.get('sv_rank_model_version'),
                    'madeline_info': case_obj.get('madeline_info'),
                    'chromograph_image_files': case_obj.get('chromograph_image_files'),
                    'chromograph_prefixes': case_obj.get('chromograph_prefixes'),
                    'vcf_files': case_obj.get('vcf_files'),
                    'has_svvariants': case_obj.get('has_svvariants'),
                    'has_strvariants': case_obj.get('has_strvariants'),
                    'is_research': case_obj.get('is_research', False),
                    'research_requested': case_obj.get('research_requested', False),
                    'multiqc': case_obj.get('multiqc'),
                    'mme_submission': case_obj.get('mme_submission'),
                    'status': updated_status
                }
            },
            return_document=pymongo.ReturnDocument.AFTER
        )

        LOG.info("Case updated")
        return updated_case

    def replace_case(self, case_obj):
        """Replace a existing case with a new one

        Keeps the object id

        Args:
            case_obj(dict)

        Returns:
            updated_case(dict)
        """
        # Todo: Figure out and describe when this method destroys a case if invoked instead of
        # update_case
        LOG.info("Saving case %s", case_obj['_id'])
        # update updated_at of case to "today"

        case_obj['updated_at'] = datetime.datetime.now()

        updated_case = self.case_collection.find_one_and_replace(
            {'_id': case_obj['_id']},
            case_obj,
            return_document=pymongo.ReturnDocument.AFTER
        )

        return updated_case

    def update_caseid(self, case_obj, family_id):
        """Update case id for a case across the database.

        This function is used when a case is a rerun or updated for another reason.

        Args:
            case_obj(dict)
            family_id(str): The new family id

        Returns:
            new_case(dict): The updated case object

        """
        new_case = deepcopy(case_obj)
        new_case['_id'] = family_id

        # update suspects and causatives
        for case_variants in ['suspects', 'causatives']:
            new_variantids = []
            for variant_id in case_obj.get(case_variants, []):
                case_variant = self.variant(variant_id)
                if not case_variant:
                    continue
                new_variantid = get_variantid(case_variant, family_id)
                new_variantids.append(new_variantid)
            new_case[case_variants] = new_variantids

        # update ACMG
        for acmg_obj in self.acmg_collection.find({'case_id': case_obj['_id']}):
            LOG.info("update ACMG classification: %s", acmg_obj['classification'])
            acmg_variant = self.variant(acmg_obj['variant_specific'])
            new_specific_id = get_variantid(acmg_variant, family_id)
            self.acmg_collection.find_one_and_update(
                {'_id': acmg_obj['_id']},
                {'$set': {'case_id': family_id, 'variant_specific': new_specific_id}},
            )

        # update events
        institute_obj = self.institute(case_obj['owner'])
        for event_obj in self.events(institute_obj, case=case_obj):
            LOG.info("update event: %s", event_obj['verb'])
            self.event_collection.find_one_and_update(
                {'_id': event_obj['_id']},
                {'$set': {'case': family_id}},
            )

        # insert the updated case
        self.case_collection.insert_one(new_case)
        # delete the old case
        self.case_collection.find_one_and_delete({'_id': case_obj['_id']})
        return new_case

    def case_sanger_variants(self, case_id):
        """Get all variants with verification ordered or
            already verified for a case.

        Accepts:
            case_id(str): a case _id

        Returns:
            case_verif_variants(dict): a dictionary like this: {
                'sanger_verified' : [list of vars],
                'sanger_ordered' : [list of vars]
            }
        """
        case_verif_variants = {
            'sanger_verified' : [],
            'sanger_ordered' : []
        }

        # Add the verified variants
        LOG.info("Fetching all sanger variants and all validated variants")
        results = {
            'sanger_verified' : self.validated(case_id=case_id),
            'sanger_ordered' : self.sanger_ordered(case_id=case_id)
        }

        for category in results:
            res = results[category]
            if not res:
                continue
            for var_id in res[0]['vars']:
                variant_obj = self.variant(case_id=case_id, document_id=var_id)
                if not variant_obj:
                    continue
                case_verif_variants[category].append(variant_obj)

        LOG.info("Nr variants with sanger verification found: %s",
                 len(case_verif_variants['sanger_verified']))
        LOG.info("Nr variants with sanger ordered found: %s",
                 len(case_verif_variants['sanger_ordered']))

        return case_verif_variants

    def update_case_sanger_variants(self, institute_obj, case_obj, case_verif_variants):
        """Update existing variants for a case according to a previous
            verification status.

            Accepts:
                institute_obj(dict): an institute object
                case_obj(dict): a case object

            Returns:
                updated_variants(dict): a dictionary like this: {
                    'updated_verified' : [list of variant ids],
                    'updated_ordered' : [list of variant ids]
                }

        """
        LOG.debug('Updating verification status for variants in case:{}'.format(case_obj['_id']))

        updated_variants = {
            'updated_verified' : [],
            'updated_ordered' : []
        }
        # update verification status for verified variants of a case
        for category in case_verif_variants:
            variants = case_verif_variants[category]
            verb = 'sanger'
            if category == 'sanger_verified':
                verb = 'validate'

            for old_var in variants:
                # new var display name should be the same as old display name:
                display_name = old_var['display_name']
                # check if variant still exists
                new_var = self.variant_collection.find_one({
                    'case_id' : case_obj['_id'],
                    'display_name': display_name }
                )

                if new_var is None: # if variant doesn't exist any more
                    continue

                # create a link to the new variant for the events
                link="/{0}/{1}/{2}".format(
                    new_var['institute'], case_obj['display_name'],new_var['_id']
                )

                old_event = self.event_collection.find_one({
                    'case' : case_obj['_id'],
                    'verb' : verb,
                    'variant_id': old_var['variant_id']
                })

                if old_event is None:
                    continue

                user_obj = self.user(old_event['user_id'])

                if category == 'sanger_verified':
                    # if a new variant coresponds to the old and
                    # there exist a verification event for the old one
                    # validate new variant as well:
                    updated_var = self.validate(
                        institute=institute_obj,
                        case=case_obj,
                        user=user_obj,
                        link=link,
                        variant=new_var,
                        validate_type=old_var.get('validation')
                    )
                    if updated_var:
                        updated_variants['updated_verified'].append(updated_var['_id'])

                else:
                    # old variant had Sanger validation ordered
                    # check old event to collect user_obj that ordered the verification:
                    # set sanger ordered status for the new variant as well:
                    updated_var = self.order_verification(
                        institute=institute_obj,
                        case=case_obj,
                        user=user_obj,
                        link=link,
                        variant=new_var
                    )
                    if updated_var:
                        updated_variants['updated_ordered'].append(updated_var['_id'])

        n_status_updated = len(updated_variants['updated_verified'])+len(updated_variants['updated_ordered'])
        LOG.info('Verification status updated for {} variants'.format(n_status_updated))
        return updated_variants


def get_variantid(variant_obj, family_id):
    """Create a new variant id.

    Args:
        variant_obj(dict)
        family_id(str)

    Returns:
        new_id(str): The new variant id
    """
    new_id = parse_document_id(
        chrom=variant_obj['chromosome'],
        pos=str(variant_obj['position']),
        ref=variant_obj['reference'],
        alt=variant_obj['alternative'],
        variant_type=variant_obj['variant_type'],
        case_id=family_id,
    )
    return new_id
