import { SimpleLocalClient, SimpleLocalMapper, type IServiceCallRequest, type IServiceCallResult, type ISimpleLocalEntitySnapshot, type ISimpleLocalSnapshot } from '../../core/index.js';
import { ModernFormsMapper } from './modern_forms.mapper.js';
import type { IModernFormsConfig, IModernFormsInfo, IModernFormsState } from './modern_forms.types.js';
import { modernFormsDefaultPath, modernFormsDefaultPort, modernFormsDefaultTimeoutMs, modernFormsProfile } from './modern_forms.types.js';

interface IModernFormsEndpoint {
  protocol: 'http' | 'https';
  host: string;
  port?: number;
  path: string;
}

export class ModernFormsClient extends SimpleLocalClient<IModernFormsConfig> {
  private cachedSnapshot?: ISimpleLocalSnapshot;

  constructor(private readonly configArg: IModernFormsConfig) {
    super(modernFormsProfile, configArg);
  }

  public async getSnapshot(forceRefreshArg = false): Promise<ISimpleLocalSnapshot> {
    if (!forceRefreshArg && this.cachedSnapshot) {
      return SimpleLocalMapper.clone(this.cachedSnapshot);
    }

    if (this.hasLiveHttpConfig()) {
      try {
        this.cachedSnapshot = await this.fetchSnapshot();
      } catch (errorArg) {
        this.cachedSnapshot = ModernFormsMapper.toSnapshot({ config: this.configArg, online: false, source: 'runtime', error: errorMessage(errorArg) });
      }
      return SimpleLocalMapper.clone(this.cachedSnapshot);
    }

    return super.getSnapshot(forceRefreshArg);
  }

  public async refresh(): Promise<IServiceCallResult> {
    this.cachedSnapshot = undefined;
    const snapshot = await this.getSnapshot(true);
    const success = snapshot.online && !snapshot.error;
    return { success, error: success ? undefined : snapshot.error, data: snapshot };
  }

  public async execute(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (this.configArg.client?.execute || this.configArg.commandExecutor) {
      return super.execute(requestArg);
    }
    if (!this.hasLiveHttpConfig()) {
      return super.execute(requestArg);
    }

    try {
      const commands = await this.commandsForRequest(requestArg);
      const response = await this.requestJson<Record<string, unknown>>(commands);
      this.cachedSnapshot = undefined;
      return { success: true, data: { commands, response } };
    } catch (errorArg) {
      return { success: false, error: errorMessage(errorArg) };
    }
  }

  private async fetchSnapshot(): Promise<ISimpleLocalSnapshot> {
    const info = await this.requestJson<IModernFormsInfo>({ queryStaticShadowData: true });
    const state = await this.requestJson<IModernFormsState>({ queryDynamicShadowData: true });
    const endpoint = this.endpoint();
    const mac = stringValue(this.configArg.macAddress) || stringValue(info.mac);
    const normalizedMac = mac?.replace(/:/g, '').toUpperCase();
    const deviceId = this.configArg.uniqueId || this.configArg.deviceId || normalizedMac || (endpoint ? `${endpoint.host}:${endpoint.port || modernFormsDefaultPort}` : modernFormsProfile.domain);
    const rawData = {
      device: {
        id: deviceId,
        name: this.configArg.name || stringValue(info.deviceName) || modernFormsProfile.defaultName,
        manufacturer: 'Modern Forms',
        model: stringValue(info.fanType) || modernFormsProfile.model,
        serialNumber: normalizedMac,
        host: endpoint?.host,
        port: endpoint?.port || modernFormsDefaultPort,
        protocol: endpoint?.protocol || 'http',
        configurationUrl: endpoint ? `${endpoint.protocol}://${endpoint.host}${endpoint.port && endpoint.port !== modernFormsDefaultPort ? `:${endpoint.port}` : ''}` : undefined,
        attributes: cleanAttributes({ ...info }),
      },
      info,
      state,
      entities: this.entitiesFromState(deviceId, info, state),
      fetchedAt: new Date().toISOString(),
    };

    return ModernFormsMapper.toSnapshot({ config: this.configArg, rawData, online: true, source: 'http' });
  }

  private entitiesFromState(deviceIdArg: string, infoArg: IModernFormsInfo, stateArg: IModernFormsState): ISimpleLocalEntitySnapshot[] {
    const entities: ISimpleLocalEntitySnapshot[] = [
      {
        id: 'fan',
        uniqueId: `${deviceIdArg}_fan`,
        name: 'Fan',
        platform: 'fan',
        state: boolValue(stateArg.fanOn),
        writable: true,
        attributes: cleanAttributes({
          fanSpeed: numberValue(stateArg.fanSpeed),
          percentage: fanPercentage(stateArg.fanSpeed),
          fanDirection: stringValue(stateArg.fanDirection),
          fanSleepTimer: numberValue(stateArg.fanSleepTimer),
        }),
      },
      {
        id: 'away_mode',
        uniqueId: `${deviceIdArg}_away_mode`,
        name: 'Away Mode',
        platform: 'switch',
        state: boolValue(stateArg.awayModeEnabled),
        writable: true,
      },
      {
        id: 'adaptive_learning',
        uniqueId: `${deviceIdArg}_adaptive_learning`,
        name: 'Adaptive Learning',
        platform: 'switch',
        state: boolValue(stateArg.adaptiveLearning),
        writable: true,
      },
      timerEntity(deviceIdArg, 'fan', stateArg.fanSleepTimer),
      timerActiveEntity(deviceIdArg, 'fan', stateArg.fanSleepTimer),
    ];

    if (stringValue(infoArg.lightType) || stateArg.lightOn !== undefined || stateArg.lightBrightness !== undefined) {
      entities.push(
        {
          id: 'light',
          uniqueId: `${deviceIdArg}_light`,
          name: 'Light',
          platform: 'light',
          state: boolValue(stateArg.lightOn),
          writable: true,
          attributes: cleanAttributes({
            brightness: brightnessFromPercent(stateArg.lightBrightness),
            brightnessPercent: numberValue(stateArg.lightBrightness),
            lightSleepTimer: numberValue(stateArg.lightSleepTimer),
          }),
        },
        timerEntity(deviceIdArg, 'light', stateArg.lightSleepTimer),
        timerActiveEntity(deviceIdArg, 'light', stateArg.lightSleepTimer),
      );
    }

    return entities;
  }

  private async commandsForRequest(requestArg: IServiceCallRequest): Promise<Record<string, unknown>> {
    if (requestArg.service === 'set_fan_sleep_timer') {
      return { fanSleepTimer: sleepTimestamp(requestArg) };
    }
    if (requestArg.service === 'clear_fan_sleep_timer') {
      return { fanSleepTimer: 0 };
    }
    if (requestArg.service === 'set_light_sleep_timer') {
      return { lightSleepTimer: sleepTimestamp(requestArg) };
    }
    if (requestArg.service === 'clear_light_sleep_timer') {
      return { lightSleepTimer: 0 };
    }

    if (!['turn_on', 'turn_off', 'toggle'].includes(requestArg.service)) {
      throw new Error(`Unsupported Modern Forms service: ${requestArg.service}`);
    }

    const target = this.targetType(requestArg);
    const on = requestArg.service === 'toggle' ? await this.toggledState(target, requestArg) : requestArg.service === 'turn_on';
    if (target === 'fan') {
      return cleanAttributes({ fanOn: on, fanSpeed: requestArg.service === 'turn_on' ? fanSpeedFromRequest(requestArg) : undefined, fanDirection: fanDirectionFromRequest(requestArg) });
    }
    if (target === 'light') {
      return cleanAttributes({ lightOn: on, lightBrightness: requestArg.service === 'turn_on' ? lightBrightnessFromRequest(requestArg) : undefined });
    }
    if (target === 'away_mode') {
      return { awayModeEnabled: on, queryDynamicShadowData: true };
    }
    if (target === 'adaptive_learning') {
      return { adaptiveLearning: on, queryDynamicShadowData: true };
    }
    throw new Error('Modern Forms turn_on/turn_off/toggle requires fan, light, away_mode, or adaptive_learning target context.');
  }

  private targetType(requestArg: IServiceCallRequest): 'fan' | 'light' | 'away_mode' | 'adaptive_learning' | undefined {
    const text = `${requestArg.domain} ${requestArg.target.entityId || ''} ${requestArg.target.deviceId || ''} ${requestArg.data?.target || ''} ${requestArg.data?.entity || ''} ${requestArg.data?.switch || ''}`.toLowerCase();
    if (text.includes('adaptive')) {
      return 'adaptive_learning';
    }
    if (text.includes('away')) {
      return 'away_mode';
    }
    if (text.includes('light')) {
      return 'light';
    }
    if (text.includes('fan')) {
      return 'fan';
    }
    return undefined;
  }

  private async toggledState(targetArg: 'fan' | 'light' | 'away_mode' | 'adaptive_learning' | undefined, requestArg: IServiceCallRequest): Promise<boolean> {
    if (!targetArg) {
      throw new Error('Modern Forms toggle requires a fan, light, away_mode, or adaptive_learning target.');
    }
    const snapshot = await this.getSnapshot(true);
    const current = snapshot.entities.find((entityArg) => entityArg.id === targetArg || entityArg.platform === requestArg.domain)?.state;
    return !boolValue(current);
  }

  private async requestJson<TValue>(commandsArg: Record<string, unknown>): Promise<TValue> {
    const endpoint = this.endpoint();
    if (!endpoint) {
      throw new Error('Modern Forms local HTTP requires config.host or config.url.');
    }

    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), this.configArg.timeoutMs || modernFormsDefaultTimeoutMs);
    try {
      const response = await globalThis.fetch(this.urlFor(endpoint), {
        method: 'POST',
        headers: cleanAttributes({
          accept: 'application/json',
          'content-type': 'application/json',
          authorization: this.configArg.username ? `Basic ${Buffer.from(`${this.configArg.username}:${this.configArg.password || ''}`).toString('base64')}` : undefined,
        }) as Record<string, string>,
        body: JSON.stringify(commandsArg),
        signal: controller.signal,
      });
      const text = await response.text();
      if (!response.ok) {
        throw new Error(`Modern Forms /mf request failed with ${response.status}: ${text.slice(0, 160)}`);
      }
      try {
        return JSON.parse(text) as TValue;
      } catch (errorArg) {
        throw new Error(`Modern Forms /mf JSON parse failed: ${errorMessage(errorArg)}`);
      }
    } finally {
      clearTimeout(timer);
    }
  }

  private hasLiveHttpConfig(): boolean {
    return Boolean(this.endpoint());
  }

  private endpoint(): IModernFormsEndpoint | undefined {
    if (this.configArg.url) {
      try {
        const url = new URL(this.configArg.url.includes('://') ? this.configArg.url : `http://${this.configArg.url}`);
        if (url.protocol !== 'http:' && url.protocol !== 'https:') {
          return undefined;
        }
        return {
          protocol: url.protocol === 'https:' ? 'https' : 'http',
          host: url.hostname,
          port: url.port ? Number(url.port) : this.configArg.port || modernFormsDefaultPort,
          path: url.pathname && url.pathname !== '/' ? url.pathname : stringValue(this.configArg.path) || modernFormsDefaultPath,
        };
      } catch {
        return undefined;
      }
    }
    if (!this.configArg.host) {
      return undefined;
    }
    return {
      protocol: this.configArg.useTls ? 'https' : 'http',
      host: this.configArg.host,
      port: this.configArg.port || modernFormsDefaultPort,
      path: stringValue(this.configArg.path) || modernFormsDefaultPath,
    };
  }

  private urlFor(endpointArg: IModernFormsEndpoint): string {
    const port = endpointArg.port && !((endpointArg.protocol === 'https' && endpointArg.port === 443) || (endpointArg.protocol === 'http' && endpointArg.port === 80)) ? `:${endpointArg.port}` : '';
    const path = endpointArg.path.startsWith('/') ? endpointArg.path : `/${endpointArg.path}`;
    return `${endpointArg.protocol}://${endpointArg.host}${port}${path}`;
  }
}

const timerEntity = (deviceIdArg: string, keyArg: 'fan' | 'light', valueArg: unknown): ISimpleLocalEntitySnapshot => ({
  id: `${keyArg}_timer_remaining_time`,
  uniqueId: `${deviceIdArg}_${keyArg}_timer_remaining_time`,
  name: `${titleCase(keyArg)} Timer Remaining Time`,
  platform: 'sensor',
  state: timerTimestamp(valueArg),
  deviceClass: 'timestamp',
  writable: false,
  attributes: cleanAttributes({ rawTimer: numberValue(valueArg) }),
});

const timerActiveEntity = (deviceIdArg: string, keyArg: 'fan' | 'light', valueArg: unknown): ISimpleLocalEntitySnapshot => ({
  id: `${keyArg}_sleep_timer_active`,
  uniqueId: `${deviceIdArg}_${keyArg}_sleep_timer_active`,
  name: `${titleCase(keyArg)} Sleep Timer Active`,
  platform: 'binary_sensor',
  state: timerActive(valueArg),
  writable: false,
});

const sleepTimestamp = (requestArg: IServiceCallRequest): number => {
  const minutes = numberValue(requestArg.data?.sleep_time ?? requestArg.data?.sleepTime ?? requestArg.data?.minutes);
  const seconds = numberValue(requestArg.data?.seconds);
  const durationSeconds = seconds ?? (minutes !== undefined ? minutes * 60 : undefined);
  if (durationSeconds === undefined || durationSeconds < 0) {
    throw new Error('Modern Forms sleep timer requires data.sleep_time minutes or data.seconds.');
  }
  return durationSeconds === 0 ? 0 : Math.floor(Date.now() / 1000) + Math.round(durationSeconds);
};

const fanSpeedFromRequest = (requestArg: IServiceCallRequest): number | undefined => {
  const speed = numberValue(requestArg.data?.speed ?? requestArg.data?.fanSpeed);
  if (speed !== undefined) {
    return clamp(Math.round(speed), 1, 6);
  }
  const percentage = numberValue(requestArg.data?.percentage ?? requestArg.data?.speed_pct ?? requestArg.data?.speedPct);
  return percentage === undefined ? undefined : clamp(Math.round((clamp(percentage, 1, 100) / 100) * 6), 1, 6);
};

const fanDirectionFromRequest = (requestArg: IServiceCallRequest): string | undefined => {
  const direction = stringValue(requestArg.data?.direction ?? requestArg.data?.fanDirection)?.toLowerCase();
  if (!direction) {
    return undefined;
  }
  if (direction !== 'forward' && direction !== 'reverse') {
    throw new Error('Modern Forms fan direction must be forward or reverse.');
  }
  return direction;
};

const lightBrightnessFromRequest = (requestArg: IServiceCallRequest): number | undefined => {
  const percent = numberValue(requestArg.data?.brightnessPercent ?? requestArg.data?.brightness_pct ?? requestArg.data?.brightness_percent);
  if (percent !== undefined) {
    return clamp(Math.round(percent), 1, 100);
  }
  const brightness = numberValue(requestArg.data?.brightness);
  return brightness === undefined ? undefined : clamp(Math.round((clamp(brightness, 1, 255) / 255) * 100), 1, 100);
};

const timerTimestamp = (valueArg: unknown): string | null => {
  const value = numberValue(valueArg);
  if (!timerActive(value)) {
    return null;
  }
  return new Date(value! * 1000).toISOString();
};

const timerActive = (valueArg: unknown): boolean => {
  const value = numberValue(valueArg);
  return Boolean(value && value * 1000 > Date.now());
};

const fanPercentage = (valueArg: unknown): number | undefined => {
  const speed = numberValue(valueArg);
  return speed === undefined ? undefined : Math.round((clamp(speed, 1, 6) / 6) * 100);
};

const brightnessFromPercent = (valueArg: unknown): number | undefined => {
  const percent = numberValue(valueArg);
  return percent === undefined ? undefined : Math.round(((clamp(percent, 1, 100) - 1) / 99) * 254 + 1);
};

const boolValue = (valueArg: unknown): boolean => valueArg === true || valueArg === 1 || valueArg === '1' || valueArg === 'true';

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

const numberValue = (valueArg: unknown): number | undefined => {
  if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
    return valueArg;
  }
  if (typeof valueArg === 'string' && valueArg.trim()) {
    const parsed = Number(valueArg.trim());
    return Number.isFinite(parsed) ? parsed : undefined;
  }
  return undefined;
};

const clamp = (valueArg: number, minArg: number, maxArg: number): number => Math.min(maxArg, Math.max(minArg, valueArg));

const cleanAttributes = (attributesArg: Record<string, unknown>): Record<string, unknown> => Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined && valueArg !== null && valueArg !== ''));

const titleCase = (valueArg: string): string => valueArg.replace(/_/g, ' ').replace(/\b\w/g, (letterArg) => letterArg.toUpperCase());

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