from __future__ import annotations
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy import or_, select
from sqlalchemy.orm import Session
from app.api.deps import current_user, require_roles
from app.core.database import get_db
from app.models.entities import (DuplicateCandidate, MasterAuditEvent, MasterChangeRequest, MasterEntity,
 MasterSourceRecord, MatchRule, Role, SurvivorshipRule, User)
from app.services.mdm import match_score, normalize, rebuild_golden

router=APIRouter(prefix='/api/v011',tags=['V011 Master Data Management'])
ELEVATED=(Role.admin,Role.director,Role.data_steward)
def now(): return datetime.now(timezone.utc)
def audit(db,u,entity,action,before=None,after=None,details=None): db.add(MasterAuditEvent(master_entity_id=entity.id if entity else None,actor_id=u.id,action=action,before_data=before or {},after_data=after or {},details=details or {}))
def serialize(e): return {'id':e.id,'entity_type':e.entity_type,'master_code':e.master_code,'legal_name':e.legal_name,'display_name':e.display_name,'tax_id':e.tax_id,'country_code':e.country_code,'status':e.status,'lifecycle_status':e.lifecycle_status,'department_id':e.department_id,'steward_id':e.steward_id,'golden_data':e.golden_data,'quality_score':e.quality_score,'source_count':e.source_count,'updated_at':e.updated_at}

class EntityIn(BaseModel):
    entity_type:str; master_code:str=Field(min_length=2,max_length=80); legal_name:str=''; display_name:str=''; tax_id:str=''; country_code:str='BRA'; department_id:str|None=None; steward_id:str|None=None; data:dict={}
class SourceIn(BaseModel): source_system:str; source_key:str; data:dict; source_priority:int=100
class MatchRuleIn(BaseModel): name:str; entity_type:str; fields:list[str]; weights:dict={}; exact_fields:list[str]=[]; threshold:int=85; auto_merge_threshold:int=98
class SurvivorshipIn(BaseModel): entity_type:str; field_name:str; strategy:str='source_priority'; source_order:list[str]=[]; required:bool=False
class ChangeIn(BaseModel): proposed_data:dict; reason:str=''; change_type:str='update'
class ReviewIn(BaseModel): decision:str; survivor_id:str|None=None

@router.get('/summary')
def summary(db:Session=Depends(get_db),u:User=Depends(current_user)):
    rows=db.scalars(select(MasterEntity)).all(); pending=db.query(DuplicateCandidate).filter_by(status='pending').count(); changes=db.query(MasterChangeRequest).filter_by(status='pending').count()
    by={}
    for r in rows: by[r.entity_type]=by.get(r.entity_type,0)+1
    return {'version':'V011','total_entities':len(rows),'by_type':by,'pending_duplicates':pending,'pending_changes':changes,'average_quality':round(sum(x.quality_score for x in rows)/len(rows),1) if rows else 0}

@router.post('/entities')
def create_entity(p:EntityIn,db:Session=Depends(get_db),u:User=Depends(require_roles(*ELEVATED))):
    if db.scalar(select(MasterEntity).where(MasterEntity.entity_type==p.entity_type,MasterEntity.master_code==p.master_code)): raise HTTPException(409,'Código mestre já existe')
    data=dict(p.data); data.update({k:v for k,v in {'legal_name':p.legal_name,'display_name':p.display_name,'tax_id':p.tax_id}.items() if v})
    e=MasterEntity(entity_type=p.entity_type,master_code=p.master_code,legal_name=p.legal_name,display_name=p.display_name,tax_id=p.tax_id,country_code=p.country_code,department_id=p.department_id,steward_id=p.steward_id or u.id,golden_data=data,created_by=u.id)
    db.add(e); db.flush(); audit(db,u,e,'entity.created',after=serialize(e)); db.commit(); db.refresh(e); return serialize(e)

@router.get('/entities')
def list_entities(entity_type:str|None=None,q:str|None=None,status:str|None=None,limit:int=100,db:Session=Depends(get_db),u:User=Depends(current_user)):
    stmt=select(MasterEntity)
    if entity_type: stmt=stmt.where(MasterEntity.entity_type==entity_type)
    if status: stmt=stmt.where(MasterEntity.status==status)
    if q: stmt=stmt.where(or_(MasterEntity.master_code.ilike(f'%{q}%'),MasterEntity.legal_name.ilike(f'%{q}%'),MasterEntity.display_name.ilike(f'%{q}%'),MasterEntity.tax_id.ilike(f'%{q}%')))
    return [serialize(x) for x in db.scalars(stmt.order_by(MasterEntity.updated_at.desc()).limit(min(max(limit,1),500))).all()]

@router.get('/entities/{entity_id}')
def entity_detail(entity_id:str,db:Session=Depends(get_db),u:User=Depends(current_user)):
    e=db.get(MasterEntity,entity_id)
    if not e: raise HTTPException(404,'Registro mestre não encontrado')
    sources=db.scalars(select(MasterSourceRecord).where(MasterSourceRecord.master_entity_id==e.id)).all()
    return {'entity':serialize(e),'sources':[{'id':s.id,'source_system':s.source_system,'source_key':s.source_key,'data':s.source_data,'source_priority':s.source_priority,'last_seen_at':s.last_seen_at} for s in sources]}

@router.post('/entities/{entity_id}/sources')
def add_source(entity_id:str,p:SourceIn,db:Session=Depends(get_db),u:User=Depends(require_roles(*ELEVATED))):
    e=db.get(MasterEntity,entity_id)
    if not e: raise HTTPException(404,'Registro mestre não encontrado')
    existing=db.scalar(select(MasterSourceRecord).where(MasterSourceRecord.source_system==p.source_system,MasterSourceRecord.source_key==p.source_key))
    if existing and existing.master_entity_id!=entity_id: raise HTTPException(409,'Registro de origem já vinculado a outro mestre')
    s=existing or MasterSourceRecord(entity_type=e.entity_type,source_system=p.source_system,source_key=p.source_key)
    s.master_entity_id=e.id; s.source_data=p.data; s.normalized_data=normalize(p.data); s.source_priority=p.source_priority; s.last_seen_at=now(); db.add(s); rebuild_golden(db,e); audit(db,u,e,'source.upserted',after=p.data,details={'source_system':p.source_system,'source_key':p.source_key}); db.commit(); return serialize(e)

@router.post('/entities/{entity_id}/rebuild')
def rebuild(entity_id:str,db:Session=Depends(get_db),u:User=Depends(require_roles(*ELEVATED))):
    e=db.get(MasterEntity,entity_id)
    if not e: raise HTTPException(404,'Registro mestre não encontrado')
    before=dict(e.golden_data or {}); rebuild_golden(db,e); audit(db,u,e,'golden.rebuilt',before=before,after=e.golden_data); db.commit(); return serialize(e)

@router.post('/entities/{entity_id}/approve')
def approve(entity_id:str,db:Session=Depends(get_db),u:User=Depends(require_roles(*ELEVATED))):
    e=db.get(MasterEntity,entity_id)
    if not e: raise HTTPException(404,'Registro mestre não encontrado')
    e.status='approved'; e.approved_by=u.id; e.approved_at=now(); audit(db,u,e,'entity.approved',after=serialize(e)); db.commit(); return serialize(e)

@router.post('/match-rules')
def create_match_rule(p:MatchRuleIn,db:Session=Depends(get_db),u:User=Depends(require_roles(*ELEVATED))):
    r=MatchRule(**p.model_dump(),created_by=u.id); db.add(r); db.commit(); return {'id':r.id}

@router.post('/survivorship-rules')
def create_survivorship(p:SurvivorshipIn,db:Session=Depends(get_db),u:User=Depends(require_roles(*ELEVATED))):
    r=SurvivorshipRule(**p.model_dump(),created_by=u.id); db.add(r); db.commit(); return {'id':r.id}

@router.post('/duplicates/scan/{entity_type}')
def scan_duplicates(entity_type:str,db:Session=Depends(get_db),u:User=Depends(require_roles(*ELEVATED))):
    rule=db.scalar(select(MatchRule).where(MatchRule.entity_type==entity_type,MatchRule.enabled.is_(True)).order_by(MatchRule.created_at.desc()))
    if not rule: raise HTTPException(422,'Crie uma regra de correspondência antes da varredura')
    rows=db.scalars(select(MasterEntity).where(MasterEntity.entity_type==entity_type,MasterEntity.lifecycle_status=='active')).all(); created=0
    for i,left in enumerate(rows):
        for right in rows[i+1:]:
            score,evidence=match_score(left.golden_data or {},right.golden_data or {},rule.fields,rule.weights,rule.exact_fields)
            if score<rule.threshold: continue
            a,b=sorted([left.id,right.id]); c=db.scalar(select(DuplicateCandidate).where(DuplicateCandidate.left_entity_id==a,DuplicateCandidate.right_entity_id==b))
            if not c: c=DuplicateCandidate(entity_type=entity_type,left_entity_id=a,right_entity_id=b,rule_id=rule.id); db.add(c); created+=1
            c.score=score; c.evidence=evidence
    db.commit(); return {'created':created,'evaluated_pairs':len(rows)*(len(rows)-1)//2}

@router.get('/duplicates')
def duplicates(status:str='pending',db:Session=Depends(get_db),u:User=Depends(current_user)):
    rows=db.scalars(select(DuplicateCandidate).where(DuplicateCandidate.status==status).order_by(DuplicateCandidate.score.desc()).limit(300)).all()
    return [{'id':x.id,'entity_type':x.entity_type,'left':serialize(db.get(MasterEntity,x.left_entity_id)),'right':serialize(db.get(MasterEntity,x.right_entity_id)),'score':x.score,'evidence':x.evidence,'status':x.status} for x in rows]

@router.post('/duplicates/{candidate_id}/review')
def review_duplicate(candidate_id:str,p:ReviewIn,db:Session=Depends(get_db),u:User=Depends(require_roles(*ELEVATED))):
    c=db.get(DuplicateCandidate,candidate_id)
    if not c or c.status!='pending': raise HTTPException(404,'Candidato não encontrado')
    left,right=db.get(MasterEntity,c.left_entity_id),db.get(MasterEntity,c.right_entity_id)
    if p.decision=='reject': c.status='rejected'
    elif p.decision=='merge':
        survivor=left if (p.survivor_id or left.id)==left.id else right; duplicate=right if survivor.id==left.id else left
        for s in db.scalars(select(MasterSourceRecord).where(MasterSourceRecord.master_entity_id==duplicate.id)).all(): s.master_entity_id=survivor.id
        duplicate.lifecycle_status='merged'; duplicate.status='retired'; duplicate.golden_data={**(duplicate.golden_data or {}),'merged_into':survivor.id}; rebuild_golden(db,survivor); c.status='merged'; audit(db,u,survivor,'duplicate.merged',details={'duplicate_id':duplicate.id,'candidate_id':c.id})
    else: raise HTTPException(422,'Decisão inválida')
    c.reviewed_by=u.id; c.reviewed_at=now(); db.commit(); return {'status':c.status}

@router.post('/entities/{entity_id}/change-requests')
def request_change(entity_id:str,p:ChangeIn,db:Session=Depends(get_db),u:User=Depends(current_user)):
    if not db.get(MasterEntity,entity_id): raise HTTPException(404,'Registro mestre não encontrado')
    r=MasterChangeRequest(master_entity_id=entity_id,requested_by=u.id,**p.model_dump()); db.add(r); db.commit(); return {'id':r.id,'status':r.status}

@router.post('/change-requests/{request_id}/{decision}')
def review_change(request_id:str,decision:str,db:Session=Depends(get_db),u:User=Depends(require_roles(*ELEVATED))):
    r=db.get(MasterChangeRequest,request_id)
    if not r or r.status!='pending': raise HTTPException(404,'Solicitação não encontrada')
    e=db.get(MasterEntity,r.master_entity_id); before=dict(e.golden_data or {})
    if decision=='approve': e.golden_data={**before,**r.proposed_data}; e.legal_name=str(e.golden_data.get('legal_name',e.legal_name)); e.display_name=str(e.golden_data.get('display_name',e.display_name)); r.status='approved'; audit(db,u,e,'change.approved',before=before,after=e.golden_data,details={'request_id':r.id})
    elif decision=='reject': r.status='rejected'
    else: raise HTTPException(422,'Decisão inválida')
    r.reviewed_by=u.id; r.reviewed_at=now(); db.commit(); return {'status':r.status}

@router.get('/audit')
def audit_events(entity_id:str|None=None,limit:int=100,db:Session=Depends(get_db),u:User=Depends(require_roles(Role.admin,Role.director,Role.data_steward,Role.auditor))):
    q=select(MasterAuditEvent)
    if entity_id: q=q.where(MasterAuditEvent.master_entity_id==entity_id)
    rows=db.scalars(q.order_by(MasterAuditEvent.created_at.desc()).limit(min(limit,500))).all()
    return [{'id':x.id,'master_entity_id':x.master_entity_id,'actor_id':x.actor_id,'action':x.action,'before_data':x.before_data,'after_data':x.after_data,'details':x.details,'created_at':x.created_at} for x in rows]
