import { SimpleLocalClient, type IServiceCallRequest, type IServiceCallResult } from '../../core/index.js';
import { MobileAppMapper } from './mobile_app.mapper.js';
import type {
  IMobileAppConfig,
  IMobileAppRegistrationRequest,
  IMobileAppRegistrationResponse,
  IMobileAppSensorState,
  IMobileAppSnapshot,
  IMobileAppWebhookPayload,
  TMobileAppWebhookType,
} from './mobile_app.types.js';
import { mobileAppDefaultPort, mobileAppDefaultTimeoutMs, mobileAppProfile } from './mobile_app.types.js';

export class MobileAppClient extends SimpleLocalClient<IMobileAppConfig> {
  private registration?: IMobileAppRegistrationResponse;

  constructor(private readonly configArg: IMobileAppConfig) {
    super(mobileAppProfile, configArg);
    this.registration = this.registrationFromConfig();
  }

  public async getSnapshot(forceRefreshArg = false): Promise<IMobileAppSnapshot> {
    if (!forceRefreshArg && this.registration) {
      return this.snapshotFromRegistration(this.registration, 'manual');
    }

    if (this.canRegister()) {
      try {
        return this.snapshotFromRegistration(await this.registerDevice(), 'http');
      } catch (errorArg) {
        return MobileAppMapper.toSnapshot({
          config: this.configArg,
          online: false,
          source: 'runtime',
          error: errorMessage(errorArg),
        });
      }
    }

    return super.getSnapshot(forceRefreshArg) as Promise<IMobileAppSnapshot>;
  }

  public async registerDevice(): Promise<IMobileAppRegistrationResponse> {
    const baseUrl = this.instanceUrl();
    const token = this.accessToken();
    if (!baseUrl || !token) {
      throw new Error('Mobile App registration requires config.url/baseUrl or host plus config.token/accessToken.');
    }

    const response = await this.requestJson<IMobileAppRegistrationResponse>(`${baseUrl}/api/mobile_app/registrations`, {
      method: 'POST',
      bearerToken: token,
      body: this.registrationPayload(),
    });
    if (!response.webhook_id) {
      throw new Error('Mobile App registration response did not include webhook_id.');
    }
    this.registration = response;
    return response;
  }

  public async postWebhook<TResponse = unknown, TData = unknown>(typeArg: TMobileAppWebhookType | string, dataArg?: TData): Promise<TResponse> {
    const registration = this.registration || this.registrationFromConfig() || await this.registerDevice();
    if (registration.secret || this.configArg.secret || this.configArg.supportsEncryption) {
      throw new Error('Mobile App encrypted webhooks require Sodium/NaCl support; this client only sends unencrypted payloads for supportsEncryption=false registrations.');
    }

    const payload: IMobileAppWebhookPayload<TData> = dataArg === undefined ? { type: typeArg } : { type: typeArg, data: dataArg };
    return this.requestJson<TResponse>(this.webhookUrl(registration), {
      method: 'POST',
      body: payload,
    });
  }

  public async getConfig(): Promise<unknown> {
    return this.postWebhook('get_config');
  }

  public async getZones(): Promise<unknown> {
    return this.postWebhook('get_zones');
  }

  public async updateLocation(dataArg: Record<string, unknown>): Promise<unknown> {
    return this.postWebhook('update_location', dataArg);
  }

  public async registerSensor(sensorArg: IMobileAppSensorState): Promise<unknown> {
    return this.postWebhook('register_sensor', sensorArg);
  }

  public async updateSensorStates(sensorsArg: IMobileAppSensorState[]): Promise<unknown> {
    return this.postWebhook('update_sensor_states', sensorsArg);
  }

  public async execute(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    try {
      if (requestArg.domain !== 'mobile_app') {
        return { success: false, error: `Unsupported Mobile App service domain: ${requestArg.domain}` };
      }

      if (requestArg.service === 'get_config') {
        return { success: true, data: await this.getConfig() };
      }
      if (requestArg.service === 'get_zones') {
        return { success: true, data: await this.getZones() };
      }
      if (requestArg.service === 'update_location') {
        return { success: true, data: await this.updateLocation(requestArg.data || {}) };
      }
      if (requestArg.service === 'register_sensor') {
        return { success: true, data: await this.registerSensor(requestArg.data as unknown as IMobileAppSensorState) };
      }
      if (requestArg.service === 'update_sensor_states') {
        const sensors = Array.isArray(requestArg.data?.sensors) ? requestArg.data.sensors as IMobileAppSensorState[] : requestArg.data as unknown as IMobileAppSensorState[];
        return { success: true, data: await this.updateSensorStates(sensors) };
      }
      if (this.isWebhookService(requestArg.service)) {
        return { success: true, data: await this.postWebhook(requestArg.service, requestArg.data) };
      }

      return super.execute(requestArg);
    } catch (errorArg) {
      return { success: false, error: errorMessage(errorArg) };
    }
  }

  private async requestJson<TResponse>(urlArg: string, optionsArg: { method: 'GET' | 'POST'; bearerToken?: string; body?: unknown }): Promise<TResponse> {
    const headers: Record<string, string> = { accept: 'application/json' };
    let body: string | undefined;
    if (optionsArg.bearerToken) {
      headers.authorization = `Bearer ${optionsArg.bearerToken}`;
    }
    if (optionsArg.body !== undefined) {
      headers['content-type'] = 'application/json';
      body = JSON.stringify(optionsArg.body);
    }

    const response = await globalThis.fetch(urlArg, {
      method: optionsArg.method,
      headers,
      body,
      signal: AbortSignal.timeout(this.configArg.timeoutMs || mobileAppDefaultTimeoutMs),
    });
    const text = await response.text();
    if (!response.ok) {
      throw new Error(`Mobile App HTTP request failed with ${response.status}: ${text.slice(0, 160)}`);
    }
    if (!text.trim()) {
      return {} as TResponse;
    }
    return JSON.parse(text) as TResponse;
  }

  private registrationPayload(): IMobileAppRegistrationRequest {
    const deviceName = this.configArg.deviceName || this.configArg.name || mobileAppProfile.defaultName || 'Mobile App';
    return {
      device_id: this.configArg.deviceId || this.configArg.uniqueId || MobileAppMapper.slug(deviceName),
      app_id: this.configArg.appId || 'smarthome_exchange',
      app_name: this.configArg.appName || 'smarthome.exchange',
      app_version: this.configArg.appVersion || '0.1.0',
      device_name: deviceName,
      manufacturer: this.configArg.manufacturer || 'smarthome.exchange',
      model: this.configArg.model || 'TypeScript Mobile App',
      os_name: this.configArg.osName || 'Node.js',
      os_version: this.configArg.osVersion || process.version,
      supports_encryption: this.configArg.supportsEncryption ?? false,
      app_data: this.configArg.appData || this.configArg.app_data,
    };
  }

  private snapshotFromRegistration(registrationArg: IMobileAppRegistrationResponse, sourceArg: 'http' | 'manual'): IMobileAppSnapshot {
    const instanceUrl = this.instanceUrl();
    const url = instanceUrl ? new URL(instanceUrl) : undefined;
    const payload = this.registrationPayload();
    return MobileAppMapper.toSnapshot({
      config: this.configArg,
      online: true,
      source: sourceArg,
      rawData: {
        device: {
          id: payload.device_id,
          name: payload.device_name,
          manufacturer: payload.manufacturer,
          model: payload.model,
          host: url?.hostname,
          port: url?.port ? Number(url.port) : url ? mobileAppDefaultPort : undefined,
          protocol: url?.protocol === 'https:' ? 'https' : 'http',
          configurationUrl: instanceUrl,
          attributes: {
            appId: payload.app_id,
            appName: payload.app_name,
            appVersion: payload.app_version,
            osName: payload.os_name,
            osVersion: payload.os_version,
            webhookId: registrationArg.webhook_id,
            cloudhookUrl: registrationArg.cloudhook_url || undefined,
            remoteUiUrl: registrationArg.remote_ui_url || undefined,
            supportsEncryption: payload.supports_encryption,
          },
        },
        entities: [
          {
            id: 'registered',
            name: 'Registered',
            platform: 'binary_sensor',
            state: true,
            attributes: { webhookId: registrationArg.webhook_id },
          },
          {
            id: 'webhook_id',
            name: 'Webhook ID',
            platform: 'sensor',
            state: registrationArg.webhook_id,
          },
        ],
      },
    });
  }

  private canRegister(): boolean {
    return Boolean(this.instanceUrl() && this.accessToken()) && !this.configArg.snapshot && this.configArg.rawData === undefined && this.configArg.rawText === undefined && !this.configArg.entities?.length && !this.configArg.client && !this.configArg.snapshotProvider;
  }

  private instanceUrl(): string | undefined {
    const explicit = stringValue(this.configArg.baseUrl) || stringValue(this.configArg.url);
    if (explicit) {
      return explicit.replace(/\/+$/g, '');
    }
    if (!this.configArg.host) {
      return undefined;
    }
    const protocol = this.configArg.useTls ? 'https' : 'http';
    const port = this.configArg.port || mobileAppDefaultPort;
    const defaultPort = protocol === 'https' ? 443 : 80;
    const portPart = port === defaultPort ? '' : `:${port}`;
    return `${protocol}://${this.configArg.host}${portPart}`;
  }

  private accessToken(): string | undefined {
    return stringValue(this.configArg.accessToken) || stringValue(this.configArg.token);
  }

  private registrationFromConfig(): IMobileAppRegistrationResponse | undefined {
    if (this.configArg.registration?.webhook_id) {
      return this.configArg.registration;
    }
    const webhookId = stringValue(this.configArg.webhookId) || stringValue(this.configArg.metadata?.webhook_id);
    if (!webhookId) {
      return undefined;
    }
    return {
      webhook_id: webhookId,
      cloudhook_url: this.configArg.cloudhookUrl || stringValue(this.configArg.metadata?.cloudhook_url),
      remote_ui_url: this.configArg.remoteUiUrl || stringValue(this.configArg.metadata?.remote_ui_url),
      secret: this.configArg.secret || stringValue(this.configArg.metadata?.secret),
    };
  }

  private webhookUrl(registrationArg: IMobileAppRegistrationResponse): string {
    if (this.configArg.webhookUrl) {
      return this.configArg.webhookUrl;
    }
    const baseUrl = this.instanceUrl();
    if (!baseUrl) {
      throw new Error('Mobile App webhook requires config.url/baseUrl or host to build /api/webhook/<webhook_id>.');
    }
    return `${baseUrl}/api/webhook/${encodeURIComponent(registrationArg.webhook_id)}`;
  }

  private isWebhookService(serviceArg: string): serviceArg is TMobileAppWebhookType {
    return ['call_service', 'conversation_process', 'fire_event', 'render_template', 'scan_tag', 'stream_camera', 'update_registration'].includes(serviceArg);
  }
}

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

const errorMessage = (errorArg: unknown): string => errorArg instanceof Error ? errorArg.message : String(errorArg);
