"""Reviewed separation of one exact saved merge; original evidence is never reversed."""
import json
from uuid import UUID, uuid5
from . import customer_review as review
from . import customer_review_models as m
from .customer_moves import chain
from .store import one, many, now, digest, audit

FIELDS=review.CONTACT_FIELDS
EFFECT=('Reactivate the original exact customer record and append new ownership moves for only the selected original jobs. '
        'Keep the original merge and every later edit, move, document, amount and receipt. Both portal accounts are revoked; '
        'sharing and publication are paused. Old invitations, passwords, sessions, feedback permissions, repeat definitions, '
        'follow-up decisions and device-note access do not return. Fresh access and any new work need separate reviews.')


def safe(c):
    return {k:c[k] for k in (*review.CONTACT,'state','notes','record_version','canonical_id')}


def access(s):
    return {k:s[k] for k in ('business_id','subject','membership_id','membership_version','role')}


def original(db,tid):
    t=one(db,'SELECT rowid AS chronology,* FROM customer_transfers WHERE id=?',(tid,))
    if not t or t['kind']!='merge':review.fail('This exact saved whole-customer merge was not found in this business.',404)
    return t,json.loads(t['plan'])


def context(db,tid):
    t,p=original(db,tid);a=review.contact(db,t['source_id']);b=review.contact(db,t['target_id'])
    corrected=one(db,'SELECT id,result FROM customer_merge_corrections WHERE merge_id=?',(tid,))
    errors=[]
    if corrected:errors.append('This merge already has a reviewed correction. Review its saved outcome and any remaining jobs separately.')
    if a['state']!='merged' or a['canonical_id']!=b['id'] or a['version']!=p['source']['version']+1 or a['record_version']!=p['source']['record_version']+1:
        errors.append('The original retired record is no longer the exact unchanged alias of this merge.')
    if b['state']!='active' or b['canonical_id'] is not None:errors.append('The retained customer must still be active and separate.')
    if any(a[k]!=p['source'][k] for k in FIELDS):errors.append('The original retired contact evidence has changed; separate recovery review is required.')
    if db.execute('SELECT 1 FROM customer_transfers WHERE rowid>? AND kind=? AND (source_id=? OR target_id=?)',(t['chronology'],'merge',b['id'],a['id'])).fetchone():
        errors.append('Further whole-customer merging has changed this relationship. Transitive separation requires a separate design.')
    if db.execute('SELECT 1 FROM jobs WHERE customer_id=?',(a['id'],)).fetchone():errors.append('The retired record unexpectedly owns work; review its history before correction.')
    if db.execute('SELECT COUNT(*) FROM customer_merge_corrections').fetchone()[0]>=5000:errors.append('The supported correction history bound has been reached.')
    originals=[]
    for old in p['jobs']:
        j=one(db,'SELECT * FROM jobs WHERE id=?',(old['id'],))
        blockers=[]
        if not j:blockers.append('The original job is missing; recovery review is required.')
        elif j['customer_id']!=b['id']:blockers.append('This job now belongs to another exact customer. It is excluded from this separation.')
        if j:blockers+=review.work_blockers(db,j['id'])
        moves=chain(db,old['id']) if j else []
        original_move=[r for r in moves if r['transfer_id']==tid]
        if len(original_move)!=1:blockers.append('The original merge has no unique retained move for this job.')
        originals.append({'original_job':old,'current_job':j,'ownership_chain':moves,
                          'eligible':not blockers,'blockers':blockers})
    original_ids={j['id'] for j in p['jobs']}
    stays=[j for j in many(db,'SELECT * FROM jobs WHERE customer_id=? ORDER BY id',(b['id'],)) if j['id'] not in original_ids]
    return {'merge':{'id':tid,'created_at':t['created_at'],'actor':t['actor'],'reason':t['reason'],'preview_hash':t['preview_hash']},
            'original_alias':p['source'],'original_retained':p['target'],'merge_retained_fields':p['retained_fields'],
            'alias':safe(a),'retained':safe(b),'original_jobs':originals,'unrelated_jobs_staying':stays,
            'fields':{k:{'before_merge':p['target'][k],'merge_retained':p['retained_fields'][k],'current':b[k],
                         'can_restore':b[k]==p['retained_fields'][k]} for k in FIELDS},
            'already_corrected':json.loads(corrected['result']) if corrected else None,'errors':errors,'effect':EFFECT}


def plan(db,payload,s):
    ctx=context(db,payload.merge_id);a,b=ctx['alias'],ctx['retained']
    if (payload.alias_id,payload.retained_id)!=(a['id'],b['id']):review.fail('Review the exact original and retained records from this saved merge.',404)
    if (payload.alias_version,payload.retained_version)!=(a['version'],b['version']):review.fail('Customer details changed. Keep your choices and review the current records again.')
    errors=list(ctx['errors']);by_id={r['original_job']['id']:r for r in ctx['original_jobs']}
    selected=[]
    for jid in sorted(payload.job_ids):
        row=by_id.get(jid)
        if not row:errors.append(jid+': this job was not part of the original merge.');continue
        errors.extend(jid+': '+message for message in row['blockers'])
        if row['current_job']:selected.append(row['current_job'])
    values={}
    for k,choice in payload.fields.model_dump().items():
        if choice=='restore_before_merge' and not ctx['fields'][k]['can_restore']:
            errors.append(k+': a later value is saved. Keep it here and review any contact edit separately.')
        values[k]=ctx['fields'][k]['before_merge'] if choice=='restore_before_merge' else b[k]
    inventory=review.work_inventory(db,b,a,selected)
    errors+=inventory['errors']
    scope='reactivation_only' if not payload.job_ids else 'all_original_jobs' if set(payload.job_ids)==set(by_id) else 'selected_original_jobs'
    result={'selection':payload.model_dump(),'context':ctx,'retained_fields':values,'work_review':inventory,
            'feedback_review':many(db,'SELECT id,customer_id,state,version,access_version FROM feedback_requests WHERE customer_id IN (?,?) ORDER BY id',(a['id'],b['id'])),
            'scope':scope,'actor_access':access(s),'dependency_hash':review.dependencies(db),
            'errors':errors,'can_confirm':not errors,'effect':EFFECT}
    result['preview_hash']=digest(review.packed(result));return result


def check_receipt_access(db,operation_id,s):
    row=one(db,'SELECT actor,plan FROM customer_merge_corrections WHERE id=?',(operation_id,))
    if row and (row['actor']!=s['subject'] or json.loads(row['plan'])['actor_access']!=access(s)):
        review.fail('The owner access used for this correction has changed. Review its history with current access.',403)


def execute(db,payload,s,operation_id):
    check_receipt_access(db,operation_id,s)
    old=one(db,'SELECT * FROM customer_merge_corrections WHERE id=?',(operation_id,))
    if old:
        saved=json.loads(old['plan'])
        if payload.model_dump()!={**saved['selection'],'preview_hash':old['preview_hash'],'reason':old['reason'],'confirmed':True}:
            review.fail('This operation ID already belongs to a different reviewed correction.')
        return json.loads(old['result'])
    if one(db,'SELECT id FROM customer_transfers WHERE id=?',(operation_id,)):review.fail('This operation ID already belongs to a customer transfer.')
    selection=m.MergeCorrectionSelection.model_validate(payload.model_dump(include=set(m.MergeCorrectionSelection.model_fields)))
    p=plan(db,selection,s)
    if p['preview_hash']!=payload.preview_hash:review.fail('Saved work, access or contact details changed. Your choices need a fresh preview; nothing was corrected.')
    if p['errors']:review.fail('This correction contains blockers. No part was saved: '+'; '.join(p['errors']))
    stamp=now();a=payload.alias_id;b=payload.retained_id
    db.execute("UPDATE customer_records SET state='active',canonical_id=NULL WHERE customer_id=?",(a,))
    db.execute('UPDATE customers SET version=version+1 WHERE id=?',(a,))
    review.record(db,a,'merge_correction.reactivate',s['subject'],payload.reason,advance=True)
    db.execute('UPDATE customers SET name=?,email=?,phone=?,address=?,version=version+1 WHERE id=?',(*(p['retained_fields'][k] for k in FIELDS),b))
    review.record(db,b,'merge_correction.retain',s['subject'],payload.reason,advance=True)
    child=None
    if payload.job_ids:
        selected=m.TransferSelection(kind='jobs',source_id=b,target_id=a,source_version=payload.retained_version+1,
                                     target_version=payload.alias_version+1,job_ids=payload.job_ids)
        child_plan=review.transfer_plan(db,selected)
        # Contact reactivation cannot broaden the previously reviewed work set.
        if any(child_plan[k]!=p['work_review'][k] for k in p['work_review']):review.fail('The exact work review changed during correction.')
        child_id=uuid5(UUID(operation_id),'customer-separation').hex
        if one(db,'SELECT id FROM customer_transfers WHERE id=?',(child_id,)):review.fail('The derived ownership operation is already in use.')
        child=review.execute_transfer(db,m.TransferConfirm(**selected.model_dump(),preview_hash=child_plan['preview_hash'],reason=payload.reason,confirmed=True),s,child_id)
    else:review.pause_sharing(db,b,a,s)
    result={'id':operation_id,'merge_id':payload.merge_id,'alias_id':a,'retained_id':b,'scope':p['scope'],
            'job_ids':sorted(payload.job_ids),'child_transfer_id':child['id'] if child else None,
            'alias_version':payload.alias_version+1+bool(child),'retained_version':payload.retained_version+1+bool(child),
            'portal_access':'Both revoked; fresh exact-customer invitation required.'}
    db.execute('INSERT INTO customer_merge_corrections VALUES(?,?,?,?,?,?,?,?,?,?,?,?)',
               (operation_id,payload.merge_id,a,b,s['subject'],payload.reason,payload.preview_hash,review.packed(p),review.packed(result),result['child_transfer_id'],stamp,now()))
    audit(db,'customer.merge.corrected',operation_id)
    return result
