import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { Logger } from 'nestjs-pino';
import { LokiClient } from './loki-client.service';

type LokiResultEntry = {
  values?: Array<[string, string]>;
  value?: [string, string];
  stream?: Record<string, string>;
};

type MailLogQuery = {
  startdate?: string;
  enddate?: string;
  type?: string;
  status?: string;
  fromaddr?: string;
  toaddr?: string;
  subject?: string;
  ipaddr?: string;
  page?: string;
  linenum?: string;
  isadmin?: string;
  mtype?: string;
};

@Injectable()
export class EverdigmMailService {
  constructor(
    private readonly logger: Logger,
    private readonly lokiClient: LokiClient
  ) {}

  async fetchUnspamList(
    viewcont?: string,
    start?: string,
    end?: string
  ): Promise<{ maillist: unknown[]; newcount: number; total: number }> {
    const normalizedViewcont = this.normalizeViewcont(viewcont, start, end);
    const cookieValue = await this.fetchHanbiroCookie();
    const url = this.buildMailUrl(normalizedViewcont);

    const response = await fetch(url.toString(), {
      method: 'GET',
      headers: {
        Accept: 'application/json',
        Cookie: `HANBIRO_GW=${cookieValue}`,
      },
    });

    const text = await response.text();
    let payload: unknown = text;
    try {
      payload = text ? JSON.parse(text) : null;
    } catch {
      payload = text;
    }

    if (!response.ok) {
      this.logger.error(
        {
          status: response.status,
          statusText: response.statusText,
          body: text.slice(0, 1000),
        },
        'Everdigm mail unspam request failed'
      );
      throw new HttpException('Everdigm mail request failed', response.status as HttpStatus);
    }

    return this.pickMailListFields(payload);
  }

  async fetchSpamDetail(mid: string): Promise<unknown> {
    const normalizedMid = this.normalizeMid(mid);
    const cookieValue = await this.fetchHanbiroCookie();
    const url = this.buildSpamDetailUrl(normalizedMid);

    const response = await fetch(url.toString(), {
      method: 'GET',
      headers: {
        Accept: 'application/json',
        Cookie: `HANBIRO_GW=${cookieValue}`,
      },
    });

    const text = await response.text();
    let payload: unknown = text;
    try {
      payload = text ? JSON.parse(text) : null;
    } catch {
      payload = text;
    }

    if (!response.ok) {
      this.logger.error(
        {
          status: response.status,
          statusText: response.statusText,
          body: text.slice(0, 1000),
          mid: normalizedMid,
        },
        'Everdigm mail spam detail request failed'
      );
      throw new HttpException('Everdigm mail request failed', response.status as HttpStatus);
    }

    return payload;
  }

  async submitSpamAction(
    mode: string,
    mids: string,
    isspamdb?: string
  ): Promise<unknown> {
    const normalizedMode = this.normalizeMode(mode);
    const normalizedMids = this.normalizeMids(mids);
    const normalizedIsspamdb = this.normalizeIsspamdb(isspamdb);
    const cookieValue = await this.fetchHanbiroCookie();
    const url = this.buildSpamProcUrl();

    const body = new URLSearchParams({
      mode: normalizedMode,
      mids: normalizedMids,
      isspamdb: normalizedIsspamdb,
    });

    const response = await fetch(url.toString(), {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        Cookie: `HANBIRO_GW=${cookieValue}`,
      },
      body,
    });

    const text = await response.text();
    let payload: unknown = text;
    try {
      payload = text ? JSON.parse(text) : null;
    } catch {
      payload = text;
    }

    if (!response.ok) {
      this.logger.error(
        {
          status: response.status,
          statusText: response.statusText,
          body: text.slice(0, 1000),
          mode: normalizedMode,
          mids: normalizedMids,
        },
        'Everdigm mail spam action request failed'
      );
      throw new HttpException('Everdigm mail request failed', response.status as HttpStatus);
    }

    return payload;
  }

  async fetchMailLogs(query: MailLogQuery): Promise<unknown> {
    const cookieValue = await this.fetchHanbiroCookie();
    const url = this.buildMailLogUrl(query);

    const response = await fetch(url.toString(), {
      method: 'GET',
      headers: {
        Accept: 'application/json',
        Cookie: `HANBIRO_GW=${cookieValue}`,
      },
    });

    const text = await response.text();
    let payload: unknown = text;
    try {
      payload = text ? JSON.parse(text) : null;
    } catch {
      payload = text;
    }

    if (!response.ok) {
      this.logger.error(
        {
          status: response.status,
          statusText: response.statusText,
          body: text.slice(0, 1000),
          query,
        },
        'Everdigm mail log request failed'
      );
      throw new HttpException('Everdigm mail request failed', response.status as HttpStatus);
    }

    return this.normalizeMailLogResponse(payload);
  }

  private pickMailListFields(payload: unknown): {
    maillist: unknown[];
    newcount: number;
    total: number;
  } {
    if (!payload || typeof payload !== 'object') {
      throw new HttpException('Invalid upstream response', HttpStatus.BAD_GATEWAY);
    }

    const record = payload as Record<string, unknown>;
    const maillist = Array.isArray(record.maillist) ? record.maillist : [];
    const newcount = this.parseCount(record.newcount);
    const total = this.parseCount(record.total);

    return { maillist, newcount, total };
  }

  private parseCount(value: unknown): number {
    if (typeof value === 'number') {
      return Number.isFinite(value) ? value : 0;
    }
    if (typeof value === 'string' && value.trim() !== '') {
      const parsed = Number.parseInt(value, 10);
      return Number.isNaN(parsed) ? 0 : parsed;
    }
    return 0;
  }

  private normalizeViewcont(viewcont?: string, start?: string, end?: string): string {
    if (viewcont) {
      if (!/^\d+,\d+$/.test(viewcont)) {
        throw new HttpException('Invalid viewcont format', HttpStatus.BAD_REQUEST);
      }
      return viewcont;
    }

    const startValue = this.parsePositiveInt(start, 0);
    const endValue = this.parsePositiveInt(end, 30);
    if (endValue <= startValue) {
      throw new HttpException('Invalid view range', HttpStatus.BAD_REQUEST);
    }

    return `${startValue},${endValue}`;
  }

  private parsePositiveInt(value: string | undefined, fallback: number): number {
    if (value === undefined || value === null || value === '') {
      return fallback;
    }
    const parsed = Number.parseInt(value, 10);
    if (Number.isNaN(parsed) || parsed < 0) {
      throw new HttpException('Invalid numeric value', HttpStatus.BAD_REQUEST);
    }
    return parsed;
  }

  private parsePositiveIntWithFallback(value: string | undefined, fallback: number): number {
    if (value === undefined || value === null || value === '') {
      return fallback;
    }
    const parsed = Number.parseInt(value, 10);
    if (Number.isNaN(parsed) || parsed < 0) {
      return fallback;
    }
    return parsed;
  }

  private buildMailUrl(viewcont: string): URL {
    const baseUrl = (process.env.EVERDIGM_MAIL_BASE_URL ?? 'https://mail.everdigm.com')
      .trim()
      .replace(/\/+$/, '');

    let targetUrl: URL;
    try {
      targetUrl = new URL('/email/list/CSpamremove', baseUrl);
    } catch {
      throw new HttpException('Invalid EVERDIGM_MAIL_BASE_URL', HttpStatus.SERVICE_UNAVAILABLE);
    }

    targetUrl.search = new URLSearchParams({
      keyword: '',
      msgsig: 'all',
      searchfild: 'all',
      type: 'unspam',
      viewcont,
    }).toString();

    return targetUrl;
  }

  private buildSpamDetailUrl(mid: string): URL {
    const baseUrl = (process.env.EVERDIGM_MAIL_BASE_URL ?? 'https://mail.everdigm.com')
      .trim()
      .replace(/\/+$/, '');

    let targetUrl: URL;
    try {
      targetUrl = new URL(`/email/CSpam/${encodeURIComponent(mid)}`, baseUrl);
    } catch {
      throw new HttpException('Invalid EVERDIGM_MAIL_BASE_URL', HttpStatus.SERVICE_UNAVAILABLE);
    }

    return targetUrl;
  }

  private buildSpamProcUrl(): URL {
    const baseUrl = (process.env.EVERDIGM_MAIL_BASE_URL ?? 'https://mail.everdigm.com')
      .trim()
      .replace(/\/+$/, '');

    let targetUrl: URL;
    try {
      targetUrl = new URL('/email/spam/spamproc', baseUrl);
    } catch {
      throw new HttpException('Invalid EVERDIGM_MAIL_BASE_URL', HttpStatus.SERVICE_UNAVAILABLE);
    }

    return targetUrl;
  }

  private buildMailLogUrl(query: MailLogQuery): URL {
    const baseUrl = (process.env.EVERDIGM_MAIL_BASE_URL ?? 'https://mail.everdigm.com')
      .trim()
      .replace(/\/+$/, '');

    let targetUrl: URL;
    try {
      targetUrl = new URL('/email/log2/maillog', baseUrl);
    } catch {
      throw new HttpException('Invalid EVERDIGM_MAIL_BASE_URL', HttpStatus.SERVICE_UNAVAILABLE);
    }

    const { startdate, enddate } = this.resolveMailLogDates(query.startdate, query.enddate);
    const params = new URLSearchParams({
      startdate,
      enddate,
      type: this.normalizeMailLogType(query.type),
      status: this.normalizeMailLogStatus(query.status),
      fromaddr: query.fromaddr ?? '',
      toaddr: query.toaddr ?? '',
      subject: query.subject ?? '',
      ipaddr: query.ipaddr ?? '',
      isadmin: query.isadmin ?? 'y',
      linenum: this.normalizeNumeric(query.linenum, 15).toString(),
      page: this.normalizeNumeric(query.page, 1).toString(),
      mtype: query.mtype ?? '',
    });

    targetUrl.search = params.toString();
    return targetUrl;
  }

  private normalizeMid(mid: string | undefined): string {
    const value = (mid ?? '').trim();
    if (!value) {
      throw new HttpException('mid is required', HttpStatus.BAD_REQUEST);
    }
    return value;
  }

  private normalizeMids(mids: string | undefined): string {
    const value = (mids ?? '').trim();
    if (!value) {
      throw new HttpException('mids is required', HttpStatus.BAD_REQUEST);
    }
    return value;
  }

  private normalizeMode(mode: string | undefined): string {
    const value = (mode ?? '').trim().toLowerCase();
    if (value === 'p' || value === 'd' || value === 'n') {
      return value;
    }

    const mapped = this.mapActionToMode(value);
    if (!mapped) {
      throw new HttpException(
        'mode/action must be one of p, d, n, pass, deny, new',
        HttpStatus.BAD_REQUEST
      );
    }
    return mapped;
  }

  private mapActionToMode(action: string): string | null {
    switch (action) {
      case 'pass':
        return 'p';
      case 'deny':
        return 'd';
      case 'new':
        return 'n';
      default:
        return null;
    }
  }

  private normalizeIsspamdb(isspamdb: string | undefined): string {
    const value = (isspamdb ?? '').trim().toLowerCase();
    return value === 'n' ? 'n' : 'y';
  }

  private normalizeMailLogResponse(payload: unknown): {
    rows: Array<Record<string, unknown>>;
    summary: {
      total: number;
      sent_total: number;
      receive_total: number;
      by_type: Record<string, number>;
      by_status: Record<string, number>;
      log_period_days: number;
    };
  } {
    if (!payload || typeof payload !== 'object') {
      throw new HttpException('Invalid upstream response', HttpStatus.BAD_GATEWAY);
    }

    const record = payload as Record<string, unknown>;
    const rawRows = Array.isArray(record.rows) ? record.rows : [];
    const rows = rawRows.map((row) => this.normalizeMailLogRow(row));

    const byType = this.extractSummaryCounts(record.chart2, [
      'spam',
      'block',
      'receive',
      'sent',
      'etc',
    ]);
    const byStatus = this.extractSummaryCounts(record.chart3, ['success', 'fail', 'delay']);

    return {
      rows,
      summary: {
        total: this.parseCount(record.tot),
        sent_total: this.parseCount(record.senttot),
        receive_total: this.parseCount(record.receivetot),
        by_type: byType,
        by_status: byStatus,
        log_period_days: this.parseCount(record.log_period),
      },
    };
  }

  private normalizeMailLogRow(row: unknown): Record<string, unknown> {
    if (!row || typeof row !== 'object') {
      return {};
    }
    const record = { ...(row as Record<string, unknown>) };

    const typeValue = typeof record.type === 'string' ? record.type : '';
    const statusValue = typeof record.status === 'string' ? record.status : '';

    const typeLabel = this.mapMailLogTypeLabel(typeValue);
    const statusLabel = this.mapMailLogStatusLabel(statusValue);

    if (typeLabel) {
      record.type_label = typeLabel;
    }
    if (statusLabel) {
      record.status_label = statusLabel;
    }

    return record;
  }

  private extractSummaryCounts(
    chart: unknown,
    keys: string[]
  ): Record<string, number> {
    const summary: Record<string, number> = {};
    for (const key of keys) {
      summary[key] = 0;
    }

    if (!chart || typeof chart !== 'object') {
      return summary;
    }
    const chartRecord = chart as Record<string, unknown>;
    const data = Array.isArray(chartRecord.data) ? chartRecord.data : [];

    for (const item of data) {
      if (!item || typeof item !== 'object') {
        continue;
      }
      const entry = item as Record<string, unknown>;
      const name = typeof entry.name === 'string' ? entry.name.toLowerCase() : '';
      if (!name) {
        continue;
      }
      const normalizedKey = this.normalizeSummaryKey(name);
      if (!normalizedKey) {
        continue;
      }
      summary[normalizedKey] = this.parseCount(entry.value);
    }

    return summary;
  }

  private normalizeSummaryKey(value: string): string | null {
    switch (value) {
      case 'spam':
        return 'spam';
      case 'block':
        return 'block';
      case 'receive':
      case 'received':
        return 'receive';
      case 'sent':
      case 'send':
        return 'sent';
      case 'etc':
        return 'etc';
      case 'success':
        return 'success';
      case 'fail':
      case 'failed':
        return 'fail';
      case 'delay':
      case 'delayed':
        return 'delay';
      default:
        return null;
    }
  }

  private mapMailLogTypeLabel(value: string): string | null {
    const normalized = value.trim().toLowerCase();
    if (!normalized) {
      return null;
    }
    if (normalized === 'p') return 'spam';
    if (normalized === 'b') return 'block';
    if (normalized === 'r') return 'receive';
    if (normalized === 's') return 'sent';
    if (['spam', 'block', 'receive', 'sent', 'etc'].includes(normalized)) {
      return normalized;
    }
    return null;
  }

  private mapMailLogStatusLabel(value: string): string | null {
    const normalized = value.trim().toLowerCase();
    if (!normalized) {
      return null;
    }
    if (normalized === 's') return 'success';
    if (normalized === 'f') return 'fail';
    if (normalized === 'd') return 'delay';
    if (['success', 'fail', 'delay'].includes(normalized)) {
      return normalized;
    }
    return null;
  }

  private normalizeMailLogType(value?: string): string {
    const normalizedInput = (value ?? '').trim().toLowerCase();
    const expanded = this.extractGrafanaMultiValue(normalizedInput);
    const normalized = expanded.length === 1 ? expanded[0] : normalizedInput;
    if (!normalized) {
      return '';
    }
    if (normalized === 'all' || normalized === '__all' || normalized === '*') {
      return '';
    }
    if (expanded.length > 1) {
      return '';
    }
    if (normalized === 'p' || normalized === 'b' || normalized === 'r' || normalized === 's') {
      return normalized;
    }
    switch (normalized) {
      case 'spam':
        return 'p';
      case 'block':
        return 'b';
      case 'receive':
      case 'received':
        return 'r';
      case 'send':
      case 'sent':
        return 's';
      default:
        throw new HttpException(
          'type must be one of p, b, r, s, spam, block, receive, send',
          HttpStatus.BAD_REQUEST
        );
    }
  }

  private normalizeMailLogStatus(value?: string): string {
    const normalizedInput = (value ?? '').trim().toLowerCase();
    const expanded = this.extractGrafanaMultiValue(normalizedInput);
    const normalized = expanded.length === 1 ? expanded[0] : normalizedInput;
    if (!normalized) {
      return '';
    }
    if (normalized === 'all' || normalized === '__all' || normalized === '*') {
      return '';
    }
    if (expanded.length > 1) {
      return '';
    }
    if (normalized === 's' || normalized === 'f' || normalized === 'd') {
      return normalized;
    }
    switch (normalized) {
      case 'success':
        return 's';
      case 'fail':
      case 'failed':
        return 'f';
      case 'delay':
      case 'delayed':
        return 'd';
      default:
        throw new HttpException(
          'status must be one of s, f, d, success, fail, delay',
          HttpStatus.BAD_REQUEST
        );
    }
  }

  private normalizeNumeric(value: string | undefined, fallback: number): number {
    if (value === undefined || value === null || value === '') {
      return fallback;
    }
    const parsed = Number.parseInt(value, 10);
    if (Number.isNaN(parsed) || parsed <= 0) {
      return fallback;
    }
    return parsed;
  }

  private extractGrafanaMultiValue(value: string): string[] {
    const trimmed = value.trim();
    if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
      return [];
    }
    const inner = trimmed.slice(1, -1);
    if (!inner) {
      return [];
    }
    return inner
      .split(',')
      .map((item) => item.trim().toLowerCase())
      .filter((item) => item.length > 0);
  }

  private resolveMailLogDates(startdate?: string, enddate?: string): {
    startdate: string;
    enddate: string;
  } {
    if (startdate && enddate) {
      return { startdate, enddate };
    }

    const end = new Date();
    const start = new Date(end.getTime() - 7 * 24 * 60 * 60 * 1000);

    return {
      startdate: startdate ?? this.formatMailLogDate(start),
      enddate: enddate ?? this.formatMailLogDate(end),
    };
  }

  private formatMailLogDate(date: Date): string {
    const pad = (value: number) => String(value).padStart(2, '0');
    return `${date.getFullYear()}/${pad(date.getMonth() + 1)}/${pad(date.getDate())} ${pad(
      date.getHours()
    )}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
  }

  private async fetchHanbiroCookie(): Promise<string> {
    let payload: unknown;
    try {
      payload = await this.queryLatestCookieLog();
    } catch (error) {
      this.logger.error({ error }, 'Failed to query Loki for everdigm mail cookie');
      throw new HttpException('Failed to query Loki', HttpStatus.BAD_GATEWAY);
    }

    const lines = this.extractLines(payload);
    const cookieValue = this.extractCookieValue(lines);
    if (cookieValue) {
      return cookieValue;
    }

    this.logger.warn('Everdigm mail cookie not found within lookback range, trying without time limit');

    let fallbackPayload: unknown;
    try {
      fallbackPayload = await this.queryLatestCookieLogNoLimit();
    } catch (error) {
      this.logger.error({ error }, 'Failed to query Loki for everdigm mail cookie (fallback)');
      throw new HttpException('Failed to query Loki', HttpStatus.BAD_GATEWAY);
    }

    const fallbackLines = this.extractLines(fallbackPayload);
    const fallbackCookieValue = this.extractCookieValue(fallbackLines);
    if (!fallbackCookieValue) {
      this.logger.error({ lines: fallbackLines.slice(0, 3) }, 'Everdigm mail cookie not found');
      throw new HttpException('Everdigm mail cookie not found', HttpStatus.BAD_GATEWAY);
    }

    return fallbackCookieValue;
  }

  private async queryLatestCookieLog(): Promise<unknown> {
    const lookbackSeconds = this.parsePositiveIntWithFallback(
      process.env.EVERDIGM_MAIL_COOKIE_LOOKBACK_SECONDS,
      86400
    );
    const end = new Date();
    const start = new Date(end.getTime() - lookbackSeconds * 1000);

    return this.lokiClient.queryRange({
      query: '{service_name="everdigm_mail_cookie"}',
      start: start.toISOString(),
      end: end.toISOString(),
      limit: 1,
      direction: 'backward',
    });
  }

  private async queryLatestCookieLogNoLimit(): Promise<unknown> {
    const end = new Date();
    const start = new Date(end.getTime() - 29 * 24 * 60 * 60 * 1000);
    return this.lokiClient.queryRange({
      query: '{service_name="everdigm_mail_cookie"}',
      start: start.toISOString(),
      end: end.toISOString(),
      limit: 1,
      direction: 'backward',
    });
  }

  private extractLines(payload: unknown): string[] {
    if (!payload || typeof payload !== 'object') {
      return [];
    }
    const data = (payload as { data?: { result?: LokiResultEntry[] } }).data;
    const results = data?.result;
    if (!Array.isArray(results)) {
      return [];
    }

    const lines: string[] = [];
    for (const entry of results) {
      if (Array.isArray(entry.values)) {
        for (const pair of entry.values) {
          if (pair?.length >= 2) {
            lines.push(String(pair[1]));
          }
        }
      } else if (Array.isArray(entry.value) && entry.value.length >= 2) {
        lines.push(String(entry.value[1]));
      }
    }
    return lines;
  }

  private extractCookieValue(lines: string[]): string | null {
    for (const line of lines) {
      const trimmed = line.trim();
      if (!trimmed) {
        continue;
      }

      if (this.looksLikeJson(trimmed)) {
        const parsed = this.parseJsonLine(trimmed);
        if (parsed && typeof parsed === 'object') {
          const candidate = this.extractCookieFromObject(parsed as Record<string, unknown>);
          if (candidate) {
            return candidate;
          }
        }
        continue;
      }

      const direct = this.cookieFromText(trimmed);
      if (direct) {
        return direct;
      }
    }
    return null;
  }

  private cookieFromText(text: string): string | null {
    const match = /HANBIRO_GW\s*=\s*([^;\s]+)/i.exec(text);
    if (match) {
      return match[1];
    }
    const altMatch = /HANBIRO_GW\s*:\s*([^;\s]+)/i.exec(text);
    if (altMatch) {
      return altMatch[1];
    }
    return null;
  }

  private parseJsonLine(line: string): unknown {
    try {
      return JSON.parse(line);
    } catch {
      return null;
    }
  }

  private extractCookieFromObject(obj: Record<string, unknown>): string | null {
    const directKeys = ['HANBIRO_GW', 'hanbiro_gw', 'hanbiroGw', 'hanbirogw'];
    for (const key of directKeys) {
      const value = obj[key];
      if (typeof value === 'string') {
        const normalized = this.normalizeCookieValue(value);
        if (normalized) {
          return normalized;
        }
      }
    }

    const cookieFields = ['cookie', 'Cookie', 'cookies'];
    for (const field of cookieFields) {
      const value = obj[field];
      if (typeof value === 'string') {
        const normalized = this.normalizeCookieValue(value);
        if (normalized) {
          return normalized;
        }
      }
      if (value && typeof value === 'object') {
        if (Array.isArray(value)) {
          for (const item of value) {
            if (!item || typeof item !== 'object') {
              continue;
            }
            const candidate = this.extractCookieFromCookieObject(item as Record<string, unknown>);
            if (candidate) {
              return candidate;
            }
          }
        } else {
          const nested = value as Record<string, unknown>;
          const candidate = this.extractCookieFromCookieObject(nested);
          if (candidate) {
            return candidate;
          }
        }
      }
    }

    const messageFields = ['message', 'msg', 'log', 'line', 'value'];
    for (const field of messageFields) {
      const value = obj[field];
      if (typeof value === 'string') {
        const fromText = this.cookieFromText(value);
        if (fromText) {
          return fromText;
        }
      }
    }

    return null;
  }

  private normalizeCookieValue(raw: string): string | null {
    const trimmed = raw.trim();
    if (!trimmed) {
      return null;
    }
    const match = /HANBIRO_GW\s*=\s*([^;\s]+)/i.exec(trimmed);
    if (match) {
      return match[1];
    }
    return null;
  }

  private extractCookieFromCookieObject(obj: Record<string, unknown>): string | null {
    const directKeys = ['HANBIRO_GW', 'hanbiro_gw', 'hanbiroGw', 'hanbirogw'];
    for (const key of directKeys) {
      const nestedValue = obj[key];
      if (typeof nestedValue === 'string') {
        const normalized = this.normalizeCookieValue(nestedValue);
        if (normalized) {
          return normalized;
        }
      }
    }

    const nameValue = obj.name;
    const valueValue = obj.value;
    if (typeof nameValue === 'string' && typeof valueValue === 'string') {
      if (nameValue.toUpperCase() === 'HANBIRO_GW') {
        return valueValue.trim();
      }
    }

    const cookieValue = obj.cookie;
    if (typeof cookieValue === 'string') {
      const normalized = this.normalizeCookieValue(cookieValue);
      if (normalized) {
        return normalized;
      }
    }

    return null;
  }

  private looksLikeJson(text: string): boolean {
    const firstChar = text[0];
    return firstChar === '{' || firstChar === '[';
  }
}
