import { z } from "zod"

export const flowHostPeerSchema = z.object({
  serverId: z.number().int().positive(),
  name: z.string(),
  publicKey: z.string().min(1),
  allowedIps: z.array(z.string().min(1)).min(1),
  address: z.string().min(1),
  endpoint: z.string().optional(),
})

export const trafficFlowSettingsDtoSchema = z.object({
  enabled: z.boolean(),
  collectorIp: z.string().min(1),
  flowListenPort: z.number().int().positive(),
  wgListenPort: z.number().int().positive(),
  prefix: z.string().min(1),
  publicEndpoint: z.string(),
  hostPublicKey: z.string(),
  hasHostPrivateKey: z.boolean(),
  hubServerId: z.number().int().positive().nullable(),
  retentionHours: z.number().int().positive(),
  topN: z.number().int().positive(),
  lastDatagramAt: z.string().nullable(),
  lastExporterIp: z.string().nullable(),
  lastError: z.string().nullable(),
  packetsReceived: z.number().int().nonnegative(),
  listenerBound: z.boolean(),
  listenerAddress: z.string().nullable(),
  peers: z.array(flowHostPeerSchema),
})

export const trafficFlowSettingsPatchSchema = z.object({
  enabled: z.boolean().optional(),
  collectorIp: z.string().min(1).optional(),
  flowListenPort: z.number().int().positive().optional(),
  wgListenPort: z.number().int().positive().optional(),
  prefix: z.string().min(1).optional(),
  publicEndpoint: z.string().optional(),
  hubServerId: z.number().int().positive().nullable().optional(),
  retentionHours: z.number().int().positive().optional(),
  topN: z.number().int().positive().max(1000).optional(),
})

export const trafficFlowOverlayRequestSchema = z.object({
  serverId: z.union([z.string(), z.number()]),
  publicEndpoint: z.string().optional(),
})

export const trafficFlowHostFileSchema = z.object({
  id: z.string(),
  label: z.string(),
  filename: z.string(),
  code: z.string(),
})

export const trafficFlowOverlayResultSchema = z.object({
  ok: z.boolean(),
  serverId: z.number().int(),
  interfaceName: z.string(),
  address: z.string(),
  publicKey: z.string(),
  linuxPeerBlock: z.string(),
  trafficFlow: z.boolean(),
  steps: z.array(z.string()),
  hostFiles: z.array(trafficFlowHostFileSchema),
})

export const flowTalkerDtoSchema = z.object({
  serverId: z.string(),
  serverName: z.string(),
  src: z.string(),
  dst: z.string(),
  proto: z.number().int(),
  protoName: z.string(),
  srcPort: z.number().int(),
  dstPort: z.number().int(),
  bytes: z.number().nonnegative(),
  packets: z.number().nonnegative(),
  bps: z.number().nonnegative(),
  inIface: z.string(),
  inIfaceIndex: z.string().optional(),
  outIface: z.string().optional(),
  nextHop: z.string().optional(),
  application: z.string().optional(),
  category: z.string().optional(),
  service: z.string().optional(),
  dstCountry: z.string().optional(),
  dstAsn: z.number().int().optional(),
  clientId: z.string().optional(),
  clientName: z.string().optional(),
  enId: z.string().optional(),
  enName: z.string().optional(),
  plane: z.string().optional(),
})

export const flowStatsDtoSchema = z.object({
  exportersOnline: z.number().int().nonnegative(),
  bytesPerMin: z.number().nonnegative(),
  uniqueSrc: z.number().int().nonnegative(),
  uniqueDst: z.number().int().nonnegative(),
  topProto: z.string(),
  talkers: z.array(flowTalkerDtoSchema),
  lastExporterIp: z.string().nullable().optional(),
  lastError: z.string().nullable().optional(),
  packetsReceived: z.number().int().nonnegative().optional(),
  lastDatagramAt: z.string().nullable().optional(),
  listenerBound: z.boolean().optional(),
  listenerAddress: z.string().nullable().optional(),
})

export type FlowHostPeer = z.infer<typeof flowHostPeerSchema>
export type TrafficFlowSettingsDto = z.infer<typeof trafficFlowSettingsDtoSchema>
export type TrafficFlowSettingsPatch = z.infer<typeof trafficFlowSettingsPatchSchema>
export type TrafficFlowOverlayResult = z.infer<typeof trafficFlowOverlayResultSchema>
export type TrafficFlowHostFile = z.infer<typeof trafficFlowHostFileSchema>
export const flowBreakdownRowSchema = z.object({
  id: z.string(),
  label: z.string(),
  bytes: z.number().nonnegative(),
  packets: z.number().nonnegative(),
  bps: z.number().nonnegative(),
  percent: z.number().nonnegative(),
})

export const flowIfaceChipSchema = z.object({
  name: z.string(),
  index: z.string(),
  bps: z.number().nonnegative(),
})

export const flowEntityCardSchema = z.object({
  id: z.string(),
  name: z.string(),
  subtitle: z.string(),
  site: z.string(),
  country: z.string(),
  status: z.enum(["online", "offline", "degraded"]),
  rxNow: z.number(),
  txNow: z.number(),
  sessions: z.number().int().nonnegative(),
  rxSeries: z.array(z.number()),
  txSeries: z.array(z.number()),
  bytes: z.number().nonnegative(),
})

export const flowMapEdgeSchema = z.object({
  fromId: z.string(),
  fromLabel: z.string(),
  fromCountry: z.string(),
  toCountry: z.string(),
  toAsn: z.number().int().nonnegative(),
  category: z.string(),
  bytes: z.number().nonnegative(),
  bps: z.number().nonnegative(),
})

export const flowPathRowSchema = z.object({
  id: z.string(),
  clientId: z.string(),
  clientName: z.string(),
  ifaces: z.string(),
  serverId: z.string(),
  serverName: z.string(),
  inIface: z.string(),
  outIface: z.string(),
  enId: z.string(),
  enName: z.string(),
  dst: z.string(),
  service: z.string(),
  category: z.string(),
  plane: z.string(),
  bytes: z.number().nonnegative(),
  packets: z.number().nonnegative(),
  bps: z.number().nonnegative(),
})

export const flowAnalyticsDtoSchema = z.object({
  bpsNow: z.number().nonnegative(),
  bytes: z.number().nonnegative(),
  packets: z.number().nonnegative(),
  conversations: z.number().int().nonnegative(),
  conversationsRaw: z.number().int().nonnegative().optional(),
  uniqueSrc: z.number().int().nonnegative(),
  uniqueDst: z.number().int().nonnegative(),
  topProto: z.string(),
  topCategory: z.string().optional(),
  rxSeries: z.array(z.number()),
  txSeries: z.array(z.number()),
  applications: z.array(flowBreakdownRowSchema),
  protocols: z.array(flowBreakdownRowSchema),
  sources: z.array(flowBreakdownRowSchema),
  destinations: z.array(flowBreakdownRowSchema),
  interfaces: z.array(flowBreakdownRowSchema),
  asns: z.array(flowBreakdownRowSchema).optional(),
  countries: z.array(flowBreakdownRowSchema).optional(),
  categories: z.array(flowBreakdownRowSchema).optional(),
  services: z.array(flowBreakdownRowSchema).optional(),
  mapEdges: z.array(flowMapEdgeSchema).optional(),
  conversationsList: z.array(flowTalkerDtoSchema),
  paths: z.array(flowPathRowSchema).optional(),
  ifaces: z.array(flowIfaceChipSchema),
  live: z.boolean(),
  dedupApplied: z.boolean().optional(),
  degraded: z.boolean().optional(),
  bytesPayload: z.number().nonnegative().optional(),
  bytesOverlay: z.number().nonnegative().optional(),
  bytesMesh: z.number().nonnegative().optional(),
  bytesWire: z.number().nonnegative().optional(),
  bpsOverlay: z.number().nonnegative().optional(),
  bpsWire: z.number().nonnegative().optional(),
  excludeMeshApplied: z.boolean().optional(),
  excludeOverlayApplied: z.boolean().optional(),
})

export const flowExportersDtoSchema = z.object({
  exporters: z.array(flowEntityCardSchema),
  lastExporterIp: z.string().nullable().optional(),
  lastError: z.string().nullable().optional(),
  packetsReceived: z.number().int().nonnegative().optional(),
  lastDatagramAt: z.string().nullable().optional(),
  listenerBound: z.boolean().optional(),
  listenerAddress: z.string().nullable().optional(),
})

export const flowClientsDtoSchema = z.object({
  clients: z.array(flowEntityCardSchema),
})

export const flowMonthlyDtoSchema = z.object({
  month: z.string(),
  bytes: z.number().nonnegative(),
  countries: z.array(flowBreakdownRowSchema),
  services: z.array(flowBreakdownRowSchema),
  asns: z.array(flowBreakdownRowSchema),
})

export const flowPurgeDtoSchema = z.object({
  ok: z.literal(true),
  deleted: z.object({
    buckets: z.number().int().nonnegative(),
    minuteStats: z.number().int().nonnegative(),
    minuteDims: z.number().int().nonnegative(),
    dailyDims: z.number().int().nonnegative(),
  }),
  fileBytesBefore: z.number().int().nonnegative(),
  fileBytesAfter: z.number().int().nonnegative(),
  vacuumed: z.boolean(),
})

export const flowMapHopKindSchema = z.enum(["gre", "wan", "iface"])

export const flowMapHopDtoSchema = z.object({
  fromId: z.string(),
  fromLabel: z.string(),
  toId: z.string(),
  toLabel: z.string(),
  kind: flowMapHopKindSchema,
  iface: z.string().optional(),
  bytes: z.number().nonnegative(),
  bps: z.number().nonnegative(),
  bpsFwd: z.number().nonnegative(),
  bpsRev: z.number().nonnegative(),
})

export const flowMapHopsDtoSchema = z.object({
  hops: z.array(flowMapHopDtoSchema),
  live: z.boolean(),
  rangeMinutes: z.number().int().positive(),
  windowSec: z.number().positive(),
  dedupApplied: z.boolean(),
  excludeMeshApplied: z.boolean(),
  excludeOverlayApplied: z.boolean(),
})

export type FlowTalkerDto = z.infer<typeof flowTalkerDtoSchema>
export type FlowStatsDto = z.infer<typeof flowStatsDtoSchema>
export type FlowBreakdownRow = z.infer<typeof flowBreakdownRowSchema>
export type FlowIfaceChip = z.infer<typeof flowIfaceChipSchema>
export type FlowEntityCard = z.infer<typeof flowEntityCardSchema>
export type FlowMapEdge = z.infer<typeof flowMapEdgeSchema>
export type FlowPathRow = z.infer<typeof flowPathRowSchema>
export type FlowAnalyticsDto = z.infer<typeof flowAnalyticsDtoSchema>
export type FlowExportersDto = z.infer<typeof flowExportersDtoSchema>
export type FlowClientsDto = z.infer<typeof flowClientsDtoSchema>
export type FlowMonthlyDto = z.infer<typeof flowMonthlyDtoSchema>
export type FlowPurgeDto = z.infer<typeof flowPurgeDtoSchema>
export type FlowMapHopKind = z.infer<typeof flowMapHopKindSchema>
export type FlowMapHop = z.infer<typeof flowMapHopDtoSchema>
export type FlowMapHopsDto = z.infer<typeof flowMapHopsDtoSchema>
