import type { IAppConfig } from '../config.ts';
import type {
  ICallAnsweredEvent,
  ICallEndedEvent,
  ICallRingingEvent,
  IDeviceRegisteredEvent,
  IIncomingCallEvent,
  ILegAddedEvent,
  ILegRemovedEvent,
  ILegStateChangedEvent,
  IOutboundCallEvent,
  IOutboundCallStartedEvent,
  IProviderRegisteredEvent,
} from '../shared/proxy-events.ts';
import type {
  IActiveCall,
  ICallHistoryEntry,
  IDeviceStatus,
  IProviderStatus,
  IStatusSnapshot,
  TLegType,
} from '../shared/status.ts';

const MAX_HISTORY = 100;
const CODEC_NAMES: Record<number, string> = {
  0: 'PCMU',
  8: 'PCMA',
  9: 'G.722',
  111: 'Opus',
};

export class StatusStore {
  private appConfig: IAppConfig;
  private providerStatuses = new Map<string, IProviderStatus>();
  private deviceStatuses = new Map<string, IDeviceStatus>();
  private activeCalls = new Map<string, IActiveCall>();
  private callHistory: ICallHistoryEntry[] = [];

  constructor(appConfig: IAppConfig) {
    this.appConfig = appConfig;
    this.rebuildConfigState();
  }

  updateConfig(appConfig: IAppConfig): void {
    this.appConfig = appConfig;
    this.rebuildConfigState();
  }

  buildStatusSnapshot(
    instanceId: string,
    startTime: number,
    browserDeviceIds: string[],
    voicemailCounts: Record<string, number>,
  ): IStatusSnapshot {
    const devices = [...this.deviceStatuses.values()];
    for (const deviceId of browserDeviceIds) {
      devices.push({
        id: deviceId,
        displayName: 'Browser',
        address: null,
        port: 0,
        aor: null,
        connected: true,
        isBrowser: true,
      });
    }

    return {
      instanceId,
      uptime: Math.floor((Date.now() - startTime) / 1000),
      lanIp: this.appConfig.proxy.lanIp,
      providers: [...this.providerStatuses.values()],
      devices,
      calls: [...this.activeCalls.values()].map((call) => ({
        ...call,
        duration: Math.floor((Date.now() - call.startedAt) / 1000),
        legs: [...call.legs.values()].map((leg) => ({
          ...leg,
          pktSent: 0,
          pktReceived: 0,
          transcoding: false,
        })),
      })),
      callHistory: this.callHistory,
      contacts: this.appConfig.contacts || [],
      voicemailCounts,
    };
  }

  noteDashboardCallStarted(callId: string, number: string, providerId?: string): void {
    const call = this.getOrCreateCall(callId, 'outbound');
    call.direction = 'outbound';
    call.callerNumber = null;
    call.calleeNumber = number;
    call.providerUsed = providerId || null;
    call.state = 'setting-up';
  }

  noteProviderRegistered(data: IProviderRegisteredEvent): { wasRegistered: boolean } | null {
    const provider = this.providerStatuses.get(data.provider_id);
    if (!provider) {
      return null;
    }

    const wasRegistered = provider.registered;
    provider.registered = data.registered;
    provider.publicIp = data.public_ip;
    return { wasRegistered };
  }

  noteDeviceRegistered(data: IDeviceRegisteredEvent): boolean {
    const device = this.deviceStatuses.get(data.device_id);
    if (!device) {
      return false;
    }

    device.address = data.address;
    device.port = data.port;
    device.aor = data.aor;
    device.connected = true;
    return true;
  }

  noteIncomingCall(data: IIncomingCallEvent): void {
    const call = this.getOrCreateCall(data.call_id, 'inbound');
    call.direction = 'inbound';
    call.callerNumber = data.from_uri;
    call.calleeNumber = data.to_number;
    call.providerUsed = data.provider_id;
    if (call.state === 'setting-up') {
      call.state = 'ringing';
    }
  }

  noteOutboundDeviceCall(data: IOutboundCallEvent): void {
    const call = this.getOrCreateCall(data.call_id, 'outbound');
    call.direction = 'outbound';
    call.callerNumber = data.from_device;
    call.calleeNumber = data.to_number;
    call.providerUsed = null;
  }

  noteOutboundCallStarted(data: IOutboundCallStartedEvent): void {
    const call = this.getOrCreateCall(data.call_id, 'outbound');
    call.direction = 'outbound';
    call.callerNumber = call.callerNumber ?? null;
    call.calleeNumber = data.number;
    call.providerUsed = data.provider_id;
  }

  noteCallRinging(data: ICallRingingEvent): void {
    const call = this.getOrCreateCall(data.call_id);
    call.state = 'ringing';
  }

  noteCallAnswered(data: ICallAnsweredEvent): boolean {
    const call = this.getOrCreateCall(data.call_id);

    call.state = 'connected';

    if (data.provider_media_addr && data.provider_media_port) {
      for (const leg of call.legs.values()) {
        if (leg.type !== 'sip-provider') {
          continue;
        }

        leg.remoteMedia = `${data.provider_media_addr}:${data.provider_media_port}`;
        if (data.media_protocol) {
          leg.mediaProtocol = data.media_protocol;
        }
        if (data.media_protocol === 't38-udptl') {
          leg.codec = 'T.38';
        } else if (data.sip_pt !== undefined) {
          leg.codec = CODEC_NAMES[data.sip_pt] || `PT${data.sip_pt}`;
        }
        break;
      }
    }

    return true;
  }

  noteCallEnded(data: ICallEndedEvent): boolean {
    const call = this.activeCalls.get(data.call_id);
    if (!call) {
      return false;
    }

    this.callHistory.unshift({
      id: call.id,
      direction: call.direction,
      callerNumber: call.callerNumber,
      calleeNumber: call.calleeNumber,
      providerUsed: call.providerUsed,
      startedAt: call.startedAt,
      duration: data.duration,
      legs: [...call.legs.values()].map((leg) => ({
        id: leg.id,
        type: leg.type,
        state: leg.state,
        codec: leg.codec,
        rtpPort: leg.rtpPort,
        mediaProtocol: leg.mediaProtocol,
        remoteMedia: leg.remoteMedia,
        metadata: leg.metadata || {},
      })),
    });

    if (this.callHistory.length > MAX_HISTORY) {
      this.callHistory.pop();
    }

    this.activeCalls.delete(data.call_id);
    return true;
  }

  noteLegAdded(data: ILegAddedEvent): void {
    const call = this.getOrCreateCall(data.call_id);

    call.legs.set(data.leg_id, {
      id: data.leg_id,
      type: data.kind,
      state: data.state,
      codec: data.codec ?? null,
      rtpPort: data.rtpPort ?? null,
      mediaProtocol: data.mediaProtocol ?? null,
      remoteMedia: data.remoteMedia ?? null,
      metadata: data.metadata || {},
    });
  }

  noteLegRemoved(data: ILegRemovedEvent): void {
    this.activeCalls.get(data.call_id)?.legs.delete(data.leg_id);
  }

  noteLegStateChanged(data: ILegStateChangedEvent): void {
    const call = this.getOrCreateCall(data.call_id);

    const existingLeg = call.legs.get(data.leg_id);
    if (existingLeg) {
      existingLeg.state = data.state;
      if (data.codec !== undefined) {
        existingLeg.codec = data.codec;
      }
      if (data.rtpPort !== undefined) {
        existingLeg.rtpPort = data.rtpPort;
      }
      if (data.mediaProtocol !== undefined) {
        existingLeg.mediaProtocol = data.mediaProtocol;
      }
      if (data.remoteMedia !== undefined) {
        existingLeg.remoteMedia = data.remoteMedia;
      }
      if (data.metadata) {
        existingLeg.metadata = data.metadata;
      }
      return;
    }

    call.legs.set(data.leg_id, {
      id: data.leg_id,
      type: this.inferLegType(data.leg_id),
      state: data.state,
      codec: data.codec ?? null,
      rtpPort: data.rtpPort ?? null,
      mediaProtocol: data.mediaProtocol ?? null,
      remoteMedia: data.remoteMedia ?? null,
      metadata: data.metadata || {},
    });
  }

  private rebuildConfigState(): void {
    const nextProviderStatuses = new Map<string, IProviderStatus>();
    for (const provider of this.appConfig.providers) {
      const previous = this.providerStatuses.get(provider.id);
      nextProviderStatuses.set(provider.id, {
        id: provider.id,
        displayName: provider.displayName,
        registered: previous?.registered ?? false,
        publicIp: previous?.publicIp ?? null,
      });
    }
    this.providerStatuses = nextProviderStatuses;

    const nextDeviceStatuses = new Map<string, IDeviceStatus>();
    for (const device of this.appConfig.devices) {
      const previous = this.deviceStatuses.get(device.id);
      nextDeviceStatuses.set(device.id, {
        id: device.id,
        displayName: device.displayName,
        address: previous?.address ?? null,
        port: previous?.port ?? 0,
        aor: previous?.aor ?? null,
        connected: previous?.connected ?? false,
        isBrowser: false,
      });
    }
    this.deviceStatuses = nextDeviceStatuses;
  }

  private inferLegType(legId: string): TLegType {
    if (legId.includes('-prov')) {
      return 'sip-provider';
    }
    if (legId.includes('-dev')) {
      return 'sip-device';
    }
    return 'webrtc';
  }

  private getOrCreateCall(callId: string, direction: 'inbound' | 'outbound' = 'inbound'): IActiveCall {
    let call = this.activeCalls.get(callId);
    if (!call) {
      call = {
        id: callId,
        direction,
        callerNumber: null,
        calleeNumber: null,
        providerUsed: null,
        state: 'setting-up',
        startedAt: Date.now(),
        legs: new Map(),
      };
      this.activeCalls.set(callId, call);
    }
    return call;
  }
}
