"""Independent full DTO oracle from physical owned rows and frozen enum dictionary."""
import copy
import datetime
import json
import math
import re
from decimal import Decimal, ROUND_HALF_UP
from pathlib import Path

HERE=Path(__file__).resolve().parent
INT_STAFF={'id','gender','staff_type','nation_id','career','education','marital','sport_id','sport_event_id','user_id','store_id','is_delete'}


def strict_diff(expected,actual,path='$'):
    if type(expected) is not type(actual):
        return [{'path':path,'expected_type':type(expected).__name__,'actual_type':type(actual).__name__,'expected':expected,'actual':actual}]
    if isinstance(expected,dict):
        if set(expected)!=set(actual):
            return [{'path':path,'missing':sorted(set(expected)-set(actual)),'extra':sorted(set(actual)-set(expected))}]
        return sum((strict_diff(v,actual[k],path+'.'+k) for k,v in expected.items()),[])
    if isinstance(expected,list):
        if len(expected)!=len(actual):return [{'path':path,'expected_length':len(expected),'actual_length':len(actual)}]
        return sum((strict_diff(a,b,path+'['+str(i)+']') for i,(a,b) in enumerate(zip(expected,actual))),[])
    return [] if expected==actual else [{'path':path,'expected':expected,'actual':actual}]


def staff_row(raw):
    row=dict(raw)
    for key in INT_STAFF:row[key]=int(row[key])
    for key in ['height','weight']:row[key]=format(Decimal(str(row[key])),'.2f')
    for key in ['create_time','update_time']:
        assert re.fullmatch(r'\d{4}-\d\d-\d\d \d\d:\d\d:\d\d(?:\.0+)?',row[key])
        row[key]=row[key][:19]
    assert re.fullmatch(r'\d{4}-\d\d-\d\d',row['birthday'])
    return row


def age_for(birthday,day):
    born=datetime.date.fromisoformat(birthday)
    return max(0,day.year-born.year-((day.month,day.day)<(born.month,born.day)))


def page(rows,size):
    return {'total':len(rows),'per_page':size,'current_page':1,'last_page':math.ceil(len(rows)/size),'data':rows}


def enum_rows(dictionary,typ):
    fields={6:['enum_key','enum_type','enum_name','set_value'],7:['enum_key','enum_type','enum_name','set_value'],
            8:['enum_key','enum_type','enum_name','set_value'],9:['enum_key','enum_type','enum_name','set_value'],
            10:['enum_key','enum_pid','enum_name','set_value'],11:['enum_key','enum_pid','enum_name']}[typ]
    rows=[]
    for source in dictionary:
        if int(source['enum_type'])!=typ:continue
        row={key:source[key] for key in fields}
        # Actual source metadata: enum_key is VARCHAR(32), not a numeric column.
        for key in ['enum_pid','enum_type']:
            if key in row:row[key]=int(row[key])
        rows.append(row)
    return rows


def survey_expected(name,original,dictionary,date):
    master_type={'PROBLEM':6,'MEAL':10,'FOOD':11}[name]
    masters=[row for row in original['nutrition_meals'] if row['set_date']==date and int(row['type'])==master_type]
    assert len(masters)<=1
    master=masters[0] if masters else {}
    selected={}
    for row in original['nutrition_lack']:
        if row['set_date']!=date or int(row['is_delete'])!=0:continue
        if name=='PROBLEM' and int(row['master_id'])!=int(master.get('id',0)):continue
        if name!='PROBLEM' and int(row['type'])!=master_type:continue
        selected[(int(row['type']),int(row['reference_id']))]=row
    if name=='PROBLEM':
        score={key:0 for key in ['vb','vc','va','composite_v','antioxidant','protein','dietary_fiber','calcium','iron','zinc']}
        detail={'problem':[],'score':score,'other_text':master.get('other_text','')}
        for row in enum_rows(dictionary,6):
            row['set_value']=json.loads(row['set_value'])
            row.update(checked=3,v1=0,v2=0)
            selection=selected.get((6,int(row['enum_key'])))
            if selection:
                row['checked']=int(selection['checkout_status'])
                enabled=(int(row['enum_key']) in [9,25] and row['checked']==0) or row['checked']==1
                if enabled:
                    for key,value in row['set_value'].items():
                        if key in score:score[key]+=value
                if int(row['enum_key'])==11:row.update(v1=int(selection['v1']),v2=int(selection['v2']))
            detail['problem'].append(row)
        for typ,key in [(7,'lack'),(8,'meal'),(9,'nutrition')]:
            detail[key]=[]
            for row in enum_rows(dictionary,typ):
                row['checked']=int((typ,int(row['enum_key'])) in selected)
                detail[key].append(row)
        return detail
    roots={}
    score=0
    for row in enum_rows(dictionary,master_type):
        row['checked']=0
        selection=selected.get((master_type,int(row['enum_key'])))
        if name=='MEAL':
            row['recommend']=''
            if selection:row['checked']=1;row['recommend']=selection['recommend'];score+=int(row['set_value'])
        elif selection:row['checked']=1
        if row['enum_pid']==0:
            if name=='FOOD':row['checked']=[]
            roots[int(row['enum_key'])]=row
        elif row['enum_pid'] in roots:roots[row['enum_pid']].setdefault('children',[]).append(row)
    for root in roots.values():
        assert 'children' in root, 'Source would access absent children, do not invent them'
        for child in root['children']:
            if child['checked']==1:
                if name=='MEAL':root['checked']=child['enum_key'];root['recommend']=child['recommend']
                else:root['checked'].append(child['enum_key'])
    if name=='MEAL':return {'meals':list(roots.values()),'score':score,'other_text':master.get('other_text','')}
    return {'food':list(roots.values()),'food_allergy':master.get('food_allergy',''),'other_text':master.get('other_text',''),
            'food_Intolerance':master.get('food_intolerance',''),'food_disable':master.get('food_disable','')}


def expected_data(case,original,dictionary,day):
    name=case['name'];module=case['context'];payload=case['body']
    if name.startswith('STAFF_LIST'):
        rows=[]
        for source in original['staff']:
            if payload['search'] not in source['name']:continue
            row=staff_row(source)
            if module=='store':row.update(gender_name='男',staff_type_name='大众人群',age=age_for(row['birthday'],day))
            else:row={key:row[key] for key in ['id','name','gender']};row.update(file_url='',face_token='')
            rows.append(row)
        return {'list':page(rows,15 if module=='store' else 100)}
    if name=='STAFF_DETAIL':
        assert len(original['staff'])==1
        row=staff_row(original['staff'][0]);age=age_for(row['birthday'],day)
        if module=='store':
            row.update(users=[],nation=None,sport=None,sportEvent=None,gender_name='男',marital_name='未婚',staff_type_name='大众人群',career_name='',education_name='',age=age)
            row.update(career='',education='',bmi=float((Decimal(row['weight'])/(Decimal(row['height'])/100)**2).quantize(Decimal('.01'),rounding=ROUND_HALF_UP)),nation_name='',sport_name='',sport_event_name='')
        else:
            row={key:row[key] for key in ['id','name','gender','height','weight','birthday']}
            row.update(gender_name='男',age=age,weight_status='体重状态',health_status='健康状态',sport_habit='运动习惯',file_url='',face_token='')
        return {'detail':row}
    for prefix in ['PROBLEM','MEAL','FOOD']:
        if name in [prefix+'_DETAIL',prefix+'_EMPTY_DATE']:
            return {'detail':survey_expected(prefix,original,dictionary,payload['set_date'])}
    if name in ['CHECKED_DATE','CHECKED_EMPTY_TYPE']:
        return {'detail':sorted(row['set_date'] for row in original['nutrition_meals'] if int(row['type'])==payload['type'])}
    if name=='NUTRITION_DETAIL':
        rows=[r for r in original['nurtrition'] if r['set_date']==payload['target_date'] and int(r['is_delete'])==0]
        assert len(rows)==1
        row=dict(rows[0])
        for key in ['id','user_id','staff_id','unit_id','store_id','is_delete']:row[key]=int(row[key])
        for key in ['create_time','update_time']:
            assert re.fullmatch(r'\d{4}-\d\d-\d\d \d\d:\d\d:\d\d(?:\.0+)?',row[key])
            row[key]=row[key][:19]
        # Source accesses an absent property; both original MySQL and DM return null.
        return {'detail':{'recommend_energy':None,'nutrition_target':row}}
    if name=='UNIT':return [{'name':n,'value':i+1} for i,n in enumerate(['袋','片','粒','个','根','勺','瓶','盒','份'])]
    return None


def validate_body(case,body,original,dictionary,day):
    if set(body)!={'status','message','data','trace_id'}:return [{'envelope':'exact four keys required'}]
    if not isinstance(body['trace_id'],str) or not re.fullmatch('[a-fA-F0-9]{13,64}',body['trace_id']):return [{'trace_id':'invalid shape'}]
    data=expected_data(case,original,dictionary,day)
    if data is None:
        if case['name'] in ['STAFF_DETAIL_MISSING','STAFF_DETAIL_CROSS']:status,message=500,'用户不存在'
        elif case['name']=='STAFF_NOAUTH':status,message=401,('请先登录后再访问' if case['context']=='store' else '请重新登录后再访问')
        elif case['name']=='NUTRITION_EMPTY_DATE':status,message=500,r"Class 'app\store\service\nutrition\MealRecommend' not found"
        elif case['name']=='NUTRITION_FLAG':status,message=500,r'method not exist:think\db\Query->getTrainDates'
        elif case['name']=='PROBLEM_MISSING_PARAMETER':status,message=500,'method param miss:staff_id'
        elif case['name']=='PROBLEM_INVALID_TYPE':status,message=500,r'Argument 1 passed to app\store\controller\investigate\NutritionLack::detail() must be of the type int, string given'
        else:raise AssertionError('No explicit independent expected branch '+case['name'])
        return strict_diff({'status':status,'message':message,'data':[],'trace_id':body['trace_id']},body)
    expect={'status':200,'message':'获取成功' if case['name']=='UNIT' else 'success','data':data,'trace_id':body['trace_id']}
    return strict_diff(expect,body)
