from datetime import datetime, timezone
from fastapi import APIRouter, Depends, Header, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.api.deps import current_user
from app.core.database import get_db
from app.models.entities import (CorporateAssetV033, CorporateProcessV033, CorporateUnitV033,
    DigitalTwinRelationV033, KeyResultV033, OperatingKpiV033, OperatingRecommendationV033,
    StrategicObjectiveV033, Tenant, User)
from app.services.corporate_os_v033 import (build_unit_tree, compute_key_result_progress,
    executive_cockpit, graph_for_tenant, kpi_status, refresh_objective_progress, validate_relation)
from app.services.saas_tenancy import resolve_membership
router=APIRouter(prefix='/api/v033',tags=['V033 Corporate Operating System'])
ADMIN={'admin','director','manager','data_steward'}

def ctx(x_tenant_id:str=Header(...,alias='X-Tenant-ID'),db:Session=Depends(get_db),user:User=Depends(current_user)):
    tenant=db.get(Tenant,x_tenant_id); member=resolve_membership(db,user,x_tenant_id) if tenant else None
    if not tenant or tenant.status!='active': raise HTTPException(404,'Tenant ativo não encontrado')
    if getattr(user.role,'value',user.role) not in {'admin','director'} and not member: raise HTTPException(403,'Usuário não pertence ao tenant')
    return tenant,user

def admin(c=Depends(ctx)):
    if getattr(c[1].role,'value',c[1].role) not in ADMIN: raise HTTPException(403,'Perfil de gestão necessário')
    return c

class UnitIn(BaseModel):
    code:str=Field(min_length=2,max_length=80); name:str=Field(min_length=2,max_length=180); unit_type:str='department'; parent_unit_id:str|None=None; manager_id:str|None=None; region:str=''; country:str=''; timezone:str='America/Sao_Paulo'; metadata_json:dict=Field(default_factory=dict); active:bool=True
class ProcessIn(BaseModel):
    code:str; name:str; description:str=''; owner_id:str|None=None; owner_unit_id:str|None=None; workflow_definition_id:str|None=None; criticality:str='medium'; status:str='draft'; sla_minutes:int=0; nodes:list=Field(default_factory=list); edges:list=Field(default_factory=list); inputs:list=Field(default_factory=list); outputs:list=Field(default_factory=list)
class AssetIn(BaseModel):
    asset_type:str; code:str; name:str; description:str=''; owner_unit_id:str|None=None; owner_id:str|None=None; linked_resource_type:str=''; linked_resource_id:str=''; classification:str='internal'; status:str='active'; metadata_json:dict=Field(default_factory=dict)
class ObjectiveIn(BaseModel):
    title:str; description:str=''; owner_id:str|None=None; owner_unit_id:str|None=None; period_start:datetime|None=None; period_end:datetime|None=None; status:str='active'; tags:list=Field(default_factory=list)
class KeyResultIn(BaseModel):
    title:str; metric_code:str=''; baseline_value:float=0; target_value:float=0; current_value:float=0; unit:str='number'; direction:str='increase'; semantic_model_id:str|None=None; metric_definition:dict=Field(default_factory=dict); weight:int=100
class KpiIn(BaseModel):
    code:str; name:str; description:str=''; owner_unit_id:str|None=None; owner_id:str|None=None; semantic_model_id:str|None=None; metric_definition:dict=Field(default_factory=dict); target_value:float=0; warning_value:float=0; critical_value:float=0; current_value:float=0; unit:str='number'; direction:str='increase'; frequency:str='monthly'; active:bool=True
class RelationIn(BaseModel):
    source_type:str; source_id:str; relation_type:str; target_type:str; target_id:str; metadata_json:dict=Field(default_factory=dict)
class RecommendationIn(BaseModel):
    title:str; rationale:str=''; source_type:str='manual'; source_id:str=''; priority:str='medium'; proposed_action:dict=Field(default_factory=dict); owner_id:str|None=None

@router.get('/summary')
def summary(c=Depends(ctx),db:Session=Depends(get_db)):
    return {'version':'V033',**executive_cockpit(db,c[0].id)}

@router.get('/cockpit')
def cockpit(c=Depends(ctx),db:Session=Depends(get_db)): return executive_cockpit(db,c[0].id)

@router.post('/units',status_code=201)
def create_unit(p:UnitIn,c=Depends(admin),db:Session=Depends(get_db)):
    t,u=c
    if p.parent_unit_id:
        parent=db.get(CorporateUnitV033,p.parent_unit_id)
        if not parent or parent.tenant_id!=t.id: raise HTTPException(404,'Unidade superior não encontrada')
    item=CorporateUnitV033(tenant_id=t.id,created_by=u.id,**p.model_dump()); db.add(item); db.commit(); db.refresh(item); return item
@router.get('/units')
def units(c=Depends(ctx),db:Session=Depends(get_db)):
    return db.scalars(select(CorporateUnitV033).where(CorporateUnitV033.tenant_id==c[0].id).order_by(CorporateUnitV033.name)).all()
@router.get('/units/tree')
def unit_tree(c=Depends(ctx),db:Session=Depends(get_db)):
    items=db.scalars(select(CorporateUnitV033).where(CorporateUnitV033.tenant_id==c[0].id,CorporateUnitV033.active.is_(True))).all(); return build_unit_tree(items)

@router.post('/processes',status_code=201)
def create_process(p:ProcessIn,c=Depends(admin),db:Session=Depends(get_db)):
    t,u=c; item=CorporateProcessV033(tenant_id=t.id,created_by=u.id,**p.model_dump()); db.add(item); db.commit(); db.refresh(item); return item
@router.get('/processes')
def processes(c=Depends(ctx),db:Session=Depends(get_db)):
    return db.scalars(select(CorporateProcessV033).where(CorporateProcessV033.tenant_id==c[0].id).order_by(CorporateProcessV033.name)).all()

@router.post('/assets',status_code=201)
def create_asset(p:AssetIn,c=Depends(admin),db:Session=Depends(get_db)):
    t,u=c; item=CorporateAssetV033(tenant_id=t.id,created_by=u.id,**p.model_dump()); db.add(item); db.commit(); db.refresh(item); return item
@router.get('/assets')
def assets(asset_type:str|None=None,c=Depends(ctx),db:Session=Depends(get_db)):
    q=select(CorporateAssetV033).where(CorporateAssetV033.tenant_id==c[0].id)
    if asset_type: q=q.where(CorporateAssetV033.asset_type==asset_type)
    return db.scalars(q.order_by(CorporateAssetV033.name)).all()

@router.post('/objectives',status_code=201)
def create_objective(p:ObjectiveIn,c=Depends(admin),db:Session=Depends(get_db)):
    t,u=c; item=StrategicObjectiveV033(tenant_id=t.id,created_by=u.id,**p.model_dump()); db.add(item); db.commit(); db.refresh(item); return item
@router.get('/objectives')
def objectives(c=Depends(ctx),db:Session=Depends(get_db)):
    return db.scalars(select(StrategicObjectiveV033).where(StrategicObjectiveV033.tenant_id==c[0].id).order_by(StrategicObjectiveV033.created_at.desc())).all()
@router.post('/objectives/{objective_id}/key-results',status_code=201)
def create_key_result(objective_id:str,p:KeyResultIn,c=Depends(admin),db:Session=Depends(get_db)):
    t,u=c; objective=db.get(StrategicObjectiveV033,objective_id)
    if not objective or objective.tenant_id!=t.id: raise HTTPException(404,'Objetivo não encontrado')
    data=p.model_dump(); data['progress_percent']=compute_key_result_progress(data['baseline_value'],data['target_value'],data['current_value'],data['direction'])
    item=KeyResultV033(tenant_id=t.id,objective_id=objective.id,updated_by=u.id,**data); db.add(item); db.commit(); db.refresh(item); refresh_objective_progress(db,objective); return item
@router.patch('/key-results/{key_result_id}')
def update_key_result(key_result_id:str,current_value:float,c=Depends(admin),db:Session=Depends(get_db)):
    t,u=c; item=db.get(KeyResultV033,key_result_id)
    if not item or item.tenant_id!=t.id: raise HTTPException(404,'Resultado-chave não encontrado')
    item.current_value=current_value; item.progress_percent=compute_key_result_progress(item.baseline_value,item.target_value,current_value,item.direction); item.updated_by=u.id; item.updated_at=datetime.now(timezone.utc); db.commit(); db.refresh(item)
    objective=db.get(StrategicObjectiveV033,item.objective_id); refresh_objective_progress(db,objective); return item

@router.post('/kpis',status_code=201)
def create_kpi(p:KpiIn,c=Depends(admin),db:Session=Depends(get_db)):
    t,u=c; item=OperatingKpiV033(tenant_id=t.id,created_by=u.id,**p.model_dump()); item.status=kpi_status(item); item.last_measured_at=datetime.now(timezone.utc); db.add(item); db.commit(); db.refresh(item); return item
@router.get('/kpis')
def kpis(c=Depends(ctx),db:Session=Depends(get_db)):
    return db.scalars(select(OperatingKpiV033).where(OperatingKpiV033.tenant_id==c[0].id,OperatingKpiV033.active.is_(True)).order_by(OperatingKpiV033.name)).all()
@router.patch('/kpis/{kpi_id}/measure')
def measure_kpi(kpi_id:str,current_value:float,c=Depends(admin),db:Session=Depends(get_db)):
    item=db.get(OperatingKpiV033,kpi_id)
    if not item or item.tenant_id!=c[0].id: raise HTTPException(404,'KPI não encontrado')
    item.current_value=current_value; item.status=kpi_status(item); item.last_measured_at=datetime.now(timezone.utc); db.commit(); db.refresh(item); return item

@router.post('/relations',status_code=201)
def create_relation(p:RelationIn,c=Depends(admin),db:Session=Depends(get_db)):
    errors=validate_relation(p.source_type,p.target_type,p.relation_type)
    if errors: raise HTTPException(400,'; '.join(errors))
    t,u=c; item=DigitalTwinRelationV033(tenant_id=t.id,created_by=u.id,**p.model_dump()); db.add(item); db.commit(); db.refresh(item); return item
@router.get('/graph')
def graph(c=Depends(ctx),db:Session=Depends(get_db)): return graph_for_tenant(db,c[0].id)

@router.post('/recommendations',status_code=201)
def create_recommendation(p:RecommendationIn,c=Depends(admin),db:Session=Depends(get_db)):
    t,u=c; item=OperatingRecommendationV033(tenant_id=t.id,created_by=u.id,**p.model_dump()); db.add(item); db.commit(); db.refresh(item); return item
@router.get('/recommendations')
def recommendations(c=Depends(ctx),db:Session=Depends(get_db)):
    return db.scalars(select(OperatingRecommendationV033).where(OperatingRecommendationV033.tenant_id==c[0].id).order_by(OperatingRecommendationV033.created_at.desc()).limit(200)).all()
@router.post('/recommendations/{recommendation_id}/{decision}')
def decide_recommendation(recommendation_id:str,decision:str,c=Depends(admin),db:Session=Depends(get_db)):
    if decision not in {'approve','reject','complete'}: raise HTTPException(400,'Decisão inválida')
    t,u=c; item=db.get(OperatingRecommendationV033,recommendation_id)
    if not item or item.tenant_id!=t.id: raise HTTPException(404,'Recomendação não encontrada')
    item.status={'approve':'approved','reject':'rejected','complete':'completed'}[decision]
    if decision=='approve': item.approved_by=u.id; item.approved_at=datetime.now(timezone.utc)
    db.commit(); db.refresh(item); return item
