import { kbxApi } from '../http/generated/kbxApiClient'
import { kbxTelemetryCatalog, type KbxApiOperationId, type KbxTelemetryEventName, type KbxUxEvent } from '@kbx/contracts'

const forbiddenKeyPattern = /(phone|address|name|orderNo|customer|itemCode|barcode|keyword|query|email|businessNumber|entityId)/i
const MAX_BUFFER = 50
const FLUSH_INTERVAL_MS = 5000

function sessionId() {
  const key='kbx.telemetry.session-id'
  const current=sessionStorage.getItem(key)
  if(current) return current
  const value=crypto.randomUUID();sessionStorage.setItem(key,value);return value
}

function sanitizeAttributes(eventName:KbxTelemetryEventName, attributes:Record<string,string>|undefined){
  if(!attributes) return undefined
  const allowed=new Set<string>(kbxTelemetryCatalog[eventName].allowedAttributes as readonly string[])
  const result:Record<string,string>={}
  for(const [key,value] of Object.entries(attributes)){
    if(!allowed.has(key)) throw new Error(`Telemetry attribute '${key}' is not allowed for ${eventName}.`)
    if(forbiddenKeyPattern.test(key)) throw new Error(`Telemetry attribute '${key}' may contain business/sensitive data.`)
    const normalized=String(value).slice(0,80)
    if(normalized.length) result[key]=normalized
  }
  return Object.keys(result).length?result:undefined
}

class KbxTelemetryClient {
  private buffer:KbxUxEvent[]=[]
  private timer:number|undefined
  private experimentByScreen=new Map<string,{experimentId:string;experimentVariant:string}>()

  bindExperiment(screenId:string,experimentId:string,experimentVariant:string){this.experimentByScreen.set(screenId,{experimentId,experimentVariant})}
  clearExperiment(screenId:string){this.experimentByScreen.delete(screenId)}

  start(){if(typeof window==='undefined'||this.timer)return;this.timer=window.setInterval(()=>void this.flush(),FLUSH_INTERVAL_MS)}
  stop(){if(this.timer){window.clearInterval(this.timer);this.timer=undefined}void this.flush()}

  track(eventName:KbxTelemetryEventName,input:Omit<KbxUxEvent,'eventName'|'sessionId'|'occurredAt'>={}){
    try {
      const def=kbxTelemetryCatalog[eventName]
      if(def.requiresDuration && input.durationMs==null) return false
      const experiment=input.screenId?this.experimentByScreen.get(input.screenId):undefined
      this.buffer.push({eventName,sessionId:sessionId(),occurredAt:new Date().toISOString(),...experiment,...input,attributes:sanitizeAttributes(eventName,input.attributes)})
      if(this.buffer.length>=MAX_BUFFER) void this.flush()
      return true
    } catch {
      // Telemetry is never allowed to fail or block a business command.
      return false
    }
  }

  async flush(){
    if(!this.buffer.length)return
    const events=this.buffer.splice(0,MAX_BUFFER)
    try{await kbxApi.request('common.uxTelemetry.ingest' as KbxApiOperationId,{body:{events}})}
    catch{this.buffer.unshift(...events);if(this.buffer.length>MAX_BUFFER*4)this.buffer.splice(0,this.buffer.length-MAX_BUFFER*4)}
  }
}
export const kbxTelemetry=new KbxTelemetryClient()

export function startKbxTask(screenId:string,screenVersion:string,taskType:string){
  const id=crypto.randomUUID(), started=performance.now()
  kbxTelemetry.track('task.start',{screenId,screenVersion,taskSessionId:id,attributes:{taskType}})
  return {
    id,
    complete(result='success'){kbxTelemetry.track('task.complete',{screenId,screenVersion,taskSessionId:id,durationMs:Math.round(performance.now()-started),attributes:{taskType,result}})},
    abandon(reasonCode='cancelled'){kbxTelemetry.track('task.abandon',{screenId,screenVersion,taskSessionId:id,durationMs:Math.round(performance.now()-started),attributes:{taskType,reasonCode}})},
    interaction(interactionType:string,commandId?:string){kbxTelemetry.track('interaction.execute',{screenId,screenVersion,taskSessionId:id,attributes:{interactionType,...(commandId?{commandId}:{})}})},
  }
}
