<?php
namespace controls;
class Api
{
  static private function get_request_headers()
  {
    $headers = [];

    if ( function_exists( 'getallheaders' ) )
    {
      $headers = (array) getallheaders();
    }

    if ( isset( $_SERVER['HTTP_X_ADSPRO_API_TOKEN'] ) )
    {
      $headers['X-Adspro-Api-Token'] = (string) $_SERVER['HTTP_X_ADSPRO_API_TOKEN'];
    }

    if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) )
    {
      $headers['Authorization'] = (string) $_SERVER['HTTP_AUTHORIZATION'];
    }

    return $headers;
  }

  static private function get_request_api_token()
  {
    $headers = self::get_request_headers();

    $token = trim( (string) ( $headers['X-Adspro-Api-Token'] ?? '' ) );
    if ( $token !== '' )
    {
      return $token;
    }

    $auth = trim( (string) ( $headers['Authorization'] ?? '' ) );
    if ( stripos( $auth, 'Bearer ' ) === 0 )
    {
      $bearer_token = trim( substr( $auth, 7 ) );
      if ( $bearer_token !== '' )
      {
        return $bearer_token;
      }
    }

    return trim( (string) ( $_GET['api_token'] ?? $_POST['api_token'] ?? '' ) );
  }

  static private function get_configured_ingest_token()
  {
    global $mdb, $settings;

    $token = trim( (string) $mdb -> get( 'settings', 'setting_value', [ 'setting_key' => 'api_ingest_token' ] ) );
    if ( $token !== '' )
    {
      return $token;
    }

    return trim( (string) ( $settings['api_ingest_token'] ?? '' ) );
  }

  static private function json_error( $http_code, $message, $context = [], $log_source = 'api_ingest', $client_id = null )
  {
    http_response_code( (int) $http_code );
    echo json_encode( [ 'status' => 'error', 'message' => (string) $message ] );

    \factory\Logs::add(
      'warning',
      $log_source,
      (string) $message,
      $context,
      $client_id !== null ? (int) $client_id : null
    );

    exit;
  }

  static private function require_ingest_auth( $log_source, $client_id = null )
  {
    $configured_token = self::get_configured_ingest_token();
    if ( $configured_token === '' )
    {
      self::json_error(
        503,
        'API ingest token nie jest skonfigurowany.',
        [ 'ip' => (string) ( $_SERVER['REMOTE_ADDR'] ?? '' ) ],
        $log_source,
        $client_id
      );
    }

    $request_token = self::get_request_api_token();
    if ( $request_token === '' || !hash_equals( $configured_token, $request_token ) )
    {
      self::json_error(
        401,
        'Brak autoryzacji API.',
        [ 'ip' => (string) ( $_SERVER['REMOTE_ADDR'] ?? '' ) ],
        $log_source,
        $client_id
      );
    }
  }

  static private function normalize_customer_id( $value )
  {
    return preg_replace( '/\D+/', '', (string) $value );
  }

  static private function extract_payload_customer_id( $data )
  {
    return self::normalize_customer_id(
      (string) (
        $data['googleAdsCustomerId']
        ?? $data['google_ads_customer_id']
        ?? $data['customerId']
        ?? $data['customer_id']
        ?? ''
      )
    );
  }

  static public function campaigns_data_save()
  {
    global $mdb;

    self::require_ingest_auth( 'api_campaigns_data_save' );

    $json = file_get_contents( 'php://input' );
    $data = json_decode( $json, true );

    $client_id = (int) ( $data['clientId'] ?? $data['client_id'] ?? 0 );
    $raw_date = trim( (string) ( $data['date'] ?? '' ) );
    $date = $raw_date !== '' ? date( 'Y-m-d', strtotime( $raw_date ) ) : '';

    if ( $client_id <= 0 || !$date || !isset( $data['data'] ) || !is_array( $data['data'] ) )
    {
      self::json_error(
        422,
        'Nieprawidlowe dane wejsciowe. Oczekiwano: clientId/client_id, date, data[].',
        [ 'raw_payload' => is_array( $data ) ? array_keys( $data ) : 'invalid_json' ],
        'api_campaigns_data_save'
      );
    }

    $client_row = $mdb -> get( 'clients', [ 'id', 'google_ads_customer_id' ], [ 'id' => $client_id ] );
    if ( !$client_row )
    {
      self::json_error( 404, 'Nie znaleziono klienta.', [], 'api_campaigns_data_save', $client_id );
    }

    $payload_customer_id = self::extract_payload_customer_id( $data );
    $client_customer_id = self::normalize_customer_id( (string) ( $client_row['google_ads_customer_id'] ?? '' ) );

    if ( $payload_customer_id === '' || $client_customer_id === '' || $payload_customer_id !== $client_customer_id )
    {
      self::json_error(
        422,
        'Niezgodny customer_id dla klienta.',
        [
          'payload_customer_id' => $payload_customer_id,
          'client_customer_id' => $client_customer_id
        ],
        'api_campaigns_data_save',
        $client_id
      );
    }

    $inserted_campaigns = 0;
    $updated_campaigns = 0;
    $inserted_history = 0;
    $updated_history = 0;

    foreach ( $data['data'] as $campaign )
    {
      $campaign_id = 0;
      $external_campaign_id = trim( (string) ( $campaign['campaignId'] ?? '' ) );
      if ( $external_campaign_id === '' )
      {
        continue;
      }

      $incoming_campaign_name = trim( (string) ( $campaign['camapignName'] ?? ( $campaign['campaignName'] ?? '' ) ) );
      if ( $incoming_campaign_name === '' )
      {
        $incoming_campaign_name = 'Campaign #' . $external_campaign_id;
      }

      if ( !$mdb -> count( 'campaigns', [ 'AND' => [ 'client_id' => $client_id, 'campaign_id' => $external_campaign_id ] ] ) )
      {
        $mdb -> insert( 'campaigns', [
          'client_id' => $client_id,
          'campaign_id' => $external_campaign_id,
          'campaign_name' => $incoming_campaign_name
        ] );

        $campaign_id = $mdb -> id();
        $inserted_campaigns++;
      }
      else
      {
        $campaign_id = $mdb -> get( 'campaigns', 'id', [ 'AND' => [ 'client_id' => $client_id, 'campaign_id' => $external_campaign_id ] ] );

        if ( $campaign_id )
        {
          $existing_campaign_name = trim( (string) $mdb -> get( 'campaigns', 'campaign_name', [ 'id' => $campaign_id ] ) );
          if ( $incoming_campaign_name !== '' and $existing_campaign_name !== $incoming_campaign_name )
          {
            $mdb -> update( 'campaigns', [ 'campaign_name' => $incoming_campaign_name ], [ 'id' => $campaign_id ] );
            $updated_campaigns++;
          }
        }
      }

      if ( !$campaign_id )
      {
        continue;
      }

      $campaign_history_data = [];
      $campaign_history_data['roas_30_days'] = $campaign['roas30Days'];
      $campaign_history_data['roas_all_time'] = $campaign['roasAllTime'];
      $campaign_history_data['budget'] = self::normalize_number( $campaign['budget'] ?? 0 );
      $campaign_history_data['money_spent'] = floatval( preg_replace( [ '/[^0-9,]/', '/,/' ], [ '', '.' ], (string) ( $campaign['spend30Days'] ?? '' ) ) );
      $campaign_history_data['conversion_value'] = preg_replace( '/[^\d,.-]/', '', (string) ( $campaign['conversionValue30Days'] ?? '' ) );

      if ( isset( $campaign['biddingStrategy'] ) and $campaign['biddingStrategy'] == 'MAXIMIZE_CONVERSIONS' )
      {
        $campaign_history_data['bidding_strategy'] = 'Maksymalizacja liczby konwersji';
      }
      else if ( isset( $campaign['biddingStrategy'] ) and $campaign['biddingStrategy'] == 'MAXIMIZE_CONVERSION_VALUE' )
      {
        $campaign_history_data['bidding_strategy'] = 'Maksymalizacja wartosci konwersji';
      }
      else if ( isset( $campaign['biddingStrategy'] ) and $campaign['biddingStrategy'] == 'TARGET_ROAS' )
      {
        $campaign_history_data['bidding_strategy'] = 'Docelowy ROAS';
      }
      else if ( isset( $campaign['biddingStrategy'] ) )
      {
        $campaign_history_data['bidding_strategy'] = $campaign['biddingStrategy'];
      }
      else
      {
        $campaign_history_data['bidding_strategy'] = 'brak';
      }

      if ( isset( $campaign['targetRoas'] ) and (float) $campaign['targetRoas'] > 0 )
      {
        $campaign_history_data['bidding_strategy'] .= ' | docelowy ROAS: ' . ( (float) $campaign['targetRoas'] * 100 ) . '%';
      }

      if ( $mdb -> count( 'campaigns_history', [ 'AND' => [ 'campaign_id' => $campaign_id, 'date_add' => $date ] ] ) )
      {
        $mdb -> update( 'campaigns_history', $campaign_history_data, [ 'AND' => [ 'campaign_id' => $campaign_id, 'date_add' => $date ] ] );
        $updated_history++;
      }
      else
      {
        $campaign_history_data['campaign_id'] = $campaign_id;
        $campaign_history_data['date_add'] = $date;
        $mdb -> insert( 'campaigns_history', $campaign_history_data );
        $inserted_history++;
      }
    }

    \factory\Logs::add(
      'info',
      'api_campaigns_data_save',
      'Zapisano import kampanii z API.',
      [
        'client_id' => $client_id,
        'date' => $date,
        'source' => 'api_ingest',
        'ip' => (string) ( $_SERVER['REMOTE_ADDR'] ?? '' ),
        'campaigns_received' => count( (array) $data['data'] ),
        'campaigns_inserted' => $inserted_campaigns,
        'campaigns_updated' => $updated_campaigns,
        'history_inserted' => $inserted_history,
        'history_updated' => $updated_history
      ],
      $client_id
    );

    echo json_encode( [
      'status' => 'ok',
      'client_id' => $client_id,
      'date' => $date,
      'campaigns_received' => count( (array) $data['data'] ),
      'campaigns_inserted' => $inserted_campaigns,
      'campaigns_updated' => $updated_campaigns,
      'history_inserted' => $inserted_history,
      'history_updated' => $updated_history
    ] );
    exit;
  }
  static public function phrases_data_save()
  {
    global $mdb;

    self::require_ingest_auth( 'api_phrases_data_save' );

    $json = file_get_contents( 'php://input' );
    $data = json_decode( $json, true );

    if ( $data['client_id'] and $data['date'] )
    {
      foreach ( $data['data'] as $phrase )
      {
        $phrase_data = [];

        if ( !$mdb -> count( 'phrases', [ 'AND' => [ 'client_id' => $data['client_id'], 'phrase' => $phrase['Query'] ] ] ) )
        {
          $phrase_data['phrase'] = $phrase['Query'];

          $mdb -> insert( 'phrases', [
            'client_id' => $data['client_id'],
            'phrase' => $phrase['Query']
          ] );

          $phrase_id = $mdb -> id();
        }
        else
        {
          $phrase_id = $mdb -> get( 'phrases', 'id', [ 'AND' => [ 'client_id' => $data['client_id'], 'phrase' => $phrase['Query'] ] ] );
        }

        if ( $phrase_id )
        {
          $phrase_data['impressions'] = $phrase['Impressions'];
          $phrase_data['clicks'] = $phrase['Clicks'];
          $phrase_data['cost'] = $phrase['Cost'];
          $phrase_data['conversions'] = $phrase['Conversions'];
          $phrase_data['conversions_value'] = preg_replace( '/[^\d,.-]/', '', $phrase['ConversionValue'] );
          $phrase_data['updated'] = 1;

          if ( $mdb -> count( 'phrases_history', [ 'AND' => [ 'phrase_id' => $phrase_id, 'date_add' => $data['date'] ] ] ) )
          {
            $phrase_data_old = [];

            $phrase_data_old = $mdb -> get( 'phrases_history', '*', [ 'AND' => [ 'phrase_id' => $phrase_id, 'date_add' => $data['date'] ] ] );

            if (
              $phrase_data_old['impressions'] == $phrase_data['impressions']
              and $phrase_data_old['clicks'] == $phrase_data['clicks']
              and number_format((float)str_replace(',', '.', $phrase_data_old['cost']), 5) == number_format((float)$phrase_data['cost'], 5)
              and $phrase_data_old['conversions'] == $phrase_data['conversions']
              and number_format((float)str_replace(',', '.', $phrase_data_old['conversions_value']), 5) == number_format((float)$phrase_data['conversions_value'], 5)
            ) {
              continue;
            }

            $mdb -> update( 'phrases_history', [
              'impressions' => $phrase_data['impressions'],
              'clicks' => $phrase_data['clicks'],
              'cost' => $phrase_data['cost'],
              'conversions' => $phrase_data['conversions'],
              'conversions_value' => $phrase_data['conversions_value'],
              'updated' => 1
            ], [
              'AND' => [ 'phrase_id' => $phrase_id, 'date_add' => $data['date'] ]
            ] );
          }
          else
          {
            $mdb -> insert( 'phrases_history', [
              'phrase_id' => $phrase_id,
              'date_add' => $data['date'],
              'impressions' => $phrase_data['impressions'],
              'clicks' => $phrase_data['clicks'],
              'cost' => $phrase_data['cost'],
              'conversions' => $phrase_data['conversions'],
              'conversions_value' => $phrase_data['conversions_value'],
              'updated' => 1
            ] );
          }
        }
      }
    }

    echo json_encode( [ 'status' => 'ok' ] );
    exit;
  }

  static public function products_data_save()
  {
    global $mdb;

    self::require_ingest_auth( 'api_products_data_save' );

    $json = file_get_contents( 'php://input' );
    $data = json_decode( $json, true );
    // file_put_contents( 'tmp/products_data_save.txt', print_r( $data, true ) );
    if ( $data['client_id'] and $data['date'] )
    {
      foreach ( $data['data'] as $offer )
      {
        $offer_data = [];

        if ( !$mdb -> count( 'products', [ 'AND' => [ 'client_id' => $data['client_id'], 'offer_id' => $offer['OfferId'] ] ] ) )
        {
          $offer_data['client_id'] = $data['client_id'];
          $offer_data['offer_id'] = $offer['OfferId'];
          $offer_data['offer_name'] = $offer['ProductTitle'];

          $mdb -> insert( 'products', [
            'client_id' => $data['client_id'],
            'offer_id' => $offer['OfferId'],
            'name' => $offer['ProductTitle']
          ] );

          $offer_id = $mdb -> id();
        }
        else
        {
          $offer_id = $mdb -> get( 'products', 'id', [ 'AND' => [ 'client_id' => $data['client_id'], 'offer_id' => $offer['OfferId'] ] ] );

          $offer_current_name = $mdb -> get( 'products', 'name', [ 'AND' => [ 'client_id' => $data['client_id'], 'offer_id' => $offer['OfferId'] ] ] );

          if ( $offer_current_name != $offer['ProductTitle'] and $data['date'] == date( 'Y-m-d', strtotime( '-1 days', time() ) ) )
          {
            $mdb -> update( 'products', [ 'name' => $offer['ProductTitle'] ], [ 'AND' => [ 'client_id' => $data['client_id'], 'offer_id' => $offer['OfferId'] ] ] );
          }
        }

        if ( $offer_id )
        {
          $offer_data['impressions'] = $offer['Impressions'];
          $offer_data['clicks'] = $offer['Clicks'];
          $offer_data['ctr'] = round( $offer['Clicks'] / $offer['Impressions'], 4 ) * 100;
          $offer_data['cost'] = preg_replace( '/[^\d,.-]/', '', $offer['Cost'] );
          $offer_data['conversions'] = $offer['Conversions'];
          $offer_data['conversions_value'] = preg_replace( '/[^\d,.-]/', '', $offer['ConversionValue'] );
          $offer_data['updated'] = 1;

          if ( $mdb -> count( 'products_history', [ 'AND' => [ 'product_id' => $offer_id, 'date_add' => $data['date'] ] ] ) )
          {
            $offer_data_old = [];

            $offer_data_old = $mdb -> get( 'products_history', '*', [ 'AND' => [ 'product_id' => $offer_id, 'date_add' => $data['date'] ] ] );

            if (
              $offer_data_old['impressions'] == $offer_data['impressions']
              and $offer_data_old['clicks'] == $offer_data['clicks']
              and number_format((float)str_replace(',', '.', $offer_data_old['cost']), 5) == number_format((float)$offer_data['cost'], 5)
              and $offer_data_old['conversions'] == $offer_data['conversions']
              and number_format((float)str_replace(',', '.', $offer_data_old['conversions_value']), 5) == number_format((float)$offer_data['conversions_value'], 5)
            ) {
              continue;
            }

            $mdb -> update( 'products_history', [
              'impressions' => $offer_data['impressions'],
              'clicks' => $offer_data['clicks'],
              'ctr' => $offer_data['ctr'],
              'cost' => $offer_data['cost'],
              'conversions' => $offer_data['conversions'],
              'conversions_value' => $offer_data['conversions_value'],
              'updated' => 1
            ], [
              'AND' => [ 'product_id' => $offer_id, 'date_add' => $data['date'] ]
            ] );
          }
          else
          {
            $mdb -> insert( 'products_history', [
              'product_id' => $offer_id,
              'date_add' => $data['date'],
              'impressions' => $offer_data['impressions'],
              'clicks' => $offer_data['clicks'],
              'ctr' => $offer_data['ctr'],
              'cost' => $offer_data['cost'],
              'conversions' => $offer_data['conversions'],
              'conversions_value' => $offer_data['conversions_value'],
              'updated' => 1
            ] );
          }
        }
      }
    }

    echo json_encode( [ 'status' => 'ok' ] );
    exit;
  }

  static public function products_data_import()
  {
    global $mdb;

    self::require_ingest_auth( 'api_products_data_import' );

    $json = file_get_contents( 'php://input' );
    $data = json_decode( $json, true );

    if ( !is_array( $data ) || empty( $data['client_id'] ) || empty( $data['date'] ) || !isset( $data['data'] ) || !is_array( $data['data'] ) )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Nieprawidlowe dane wejsciowe. Oczekiwano: client_id, date, data[].' ] );
      exit;
    }

    $client_id = (int) $data['client_id'];
    $date = date( 'Y-m-d', strtotime( $data['date'] ) );

    if ( !$mdb -> count( 'clients', [ 'id' => $client_id ] ) )
    {
      echo json_encode( [ 'status' => 'error', 'message' => 'Nie znaleziono klienta o podanym ID.' ] );
      exit;
    }

    $processed = 0;
    $skipped = 0;
    $touched_scopes = [];

    foreach ( $data['data'] as $offer )
    {
      $offer_external_id = trim( (string) ( $offer['OfferId'] ?? '' ) );
      if ( $offer_external_id === '' )
      {
        $skipped++;
        continue;
      }

      $product_title = trim( (string) ( $offer['ProductTitle'] ?? '' ) );
      if ( $product_title === '' )
      {
        $product_title = $offer_external_id;
      }

      if ( !$mdb -> count( 'products', [ 'AND' => [ 'client_id' => $client_id, 'offer_id' => $offer_external_id ] ] ) )
      {
        $mdb -> insert( 'products', [
          'client_id' => $client_id,
          'offer_id' => $offer_external_id,
          'name' => $product_title
        ] );
        $product_id = $mdb -> id();
      }
      else
      {
        $product_id = $mdb -> get( 'products', 'id', [ 'AND' => [ 'client_id' => $client_id, 'offer_id' => $offer_external_id ] ] );
        $offer_current_name = $mdb -> get( 'products', 'name', [ 'AND' => [ 'client_id' => $client_id, 'offer_id' => $offer_external_id ] ] );

        if ( $offer_current_name != $product_title and $date == date( 'Y-m-d', strtotime( '-1 days', time() ) ) )
        {
          $mdb -> update( 'products', [ 'name' => $product_title ], [ 'AND' => [ 'client_id' => $client_id, 'offer_id' => $offer_external_id ] ] );
        }
      }

      if ( !$product_id )
      {
        $skipped++;
        continue;
      }

      $campaign_external_id = (int) self::normalize_number( $offer['CampaignId'] ?? ( $offer['campaign_id'] ?? 0 ) );
      $campaign_name = trim( (string) ( $offer['CampaignName'] ?? ( $offer['campaign_name'] ?? '' ) ) );
      $ad_group_external_id = (int) self::normalize_number( $offer['AdGroupId'] ?? ( $offer['ad_group_id'] ?? 0 ) );
      $ad_group_name = trim( (string) ( $offer['AdGroupName'] ?? ( $offer['ad_group_name'] ?? '' ) ) );

      $scope = \controls\Cron::resolve_products_scope_ids(
        $client_id,
        $campaign_external_id,
        $campaign_name,
        $ad_group_external_id,
        $ad_group_name,
        $date
      );

      $db_campaign_id = (int) ( $scope['campaign_id'] ?? 0 );
      $db_ad_group_id = (int) ( $scope['ad_group_id'] ?? 0 );

      $impressions = (int) round( self::normalize_number( $offer['Impressions'] ?? 0 ) );
      $clicks = (int) round( self::normalize_number( $offer['Clicks'] ?? 0 ) );
      $cost = self::normalize_number( $offer['Cost'] ?? 0 );
      $conversions = self::normalize_number( $offer['Conversions'] ?? 0 );
      $conversion_value = self::normalize_number( $offer['ConversionValue'] ?? 0 );
      $ctr = ( $impressions > 0 ) ? round( $clicks / $impressions, 4 ) * 100 : 0;

      $offer_data = [
        'impressions' => $impressions,
        'clicks' => $clicks,
        'ctr' => $ctr,
        'cost' => $cost,
        'conversions' => $conversions,
        'conversions_value' => $conversion_value,
        'updated' => 1,
        'campaign_id' => $db_campaign_id,
        'ad_group_id' => $db_ad_group_id
      ];

      if ( $mdb -> count( 'products_history', [ 'AND' => [
        'product_id' => $product_id,
        'campaign_id' => $db_campaign_id,
        'ad_group_id' => $db_ad_group_id,
        'date_add' => $date
      ] ] ) )
      {
        $offer_data_old = $mdb -> get( 'products_history', '*', [ 'AND' => [
          'product_id' => $product_id,
          'campaign_id' => $db_campaign_id,
          'ad_group_id' => $db_ad_group_id,
          'date_add' => $date
        ] ] );

        if (
          $offer_data_old['impressions'] == $offer_data['impressions']
          and $offer_data_old['clicks'] == $offer_data['clicks']
          and number_format( (float) str_replace( ',', '.', $offer_data_old['cost'] ), 5 ) == number_format( (float) $offer_data['cost'], 5 )
          and (float) $offer_data_old['conversions'] == (float) $offer_data['conversions']
          and number_format( (float) str_replace( ',', '.', $offer_data_old['conversions_value'] ), 5 ) == number_format( (float) $offer_data['conversions_value'], 5 )
        )
        {
          $scope_key = (int) $product_id . '|' . $db_campaign_id . '|' . $db_ad_group_id;
          $touched_scopes[ $scope_key ] = [
            'product_id' => (int) $product_id,
            'campaign_id' => $db_campaign_id,
            'ad_group_id' => $db_ad_group_id
          ];
          $processed++;
          continue;
        }

        $mdb -> update( 'products_history', $offer_data, [
          'AND' => [
            'product_id' => $product_id,
            'campaign_id' => $db_campaign_id,
            'ad_group_id' => $db_ad_group_id,
            'date_add' => $date
          ]
        ] );
      }
      else
      {
        $offer_data['product_id'] = $product_id;
        $offer_data['date_add'] = $date;
        $mdb -> insert( 'products_history', $offer_data );
      }

      $scope_key = (int) $product_id . '|' . $db_campaign_id . '|' . $db_ad_group_id;
      $touched_scopes[ $scope_key ] = [
        'product_id' => (int) $product_id,
        'campaign_id' => $db_campaign_id,
        'ad_group_id' => $db_ad_group_id
      ];
      $processed++;
    }

    $history_30_rows = 0;
    foreach ( $touched_scopes as $scope )
    {
      $product_id = (int) ( $scope['product_id'] ?? 0 );
      $campaign_id = (int) ( $scope['campaign_id'] ?? 0 );
      $ad_group_id = (int) ( $scope['ad_group_id'] ?? 0 );

      \controls\Cron::cron_product_history_30_save( $product_id, $date, $campaign_id, $ad_group_id );
      $mdb -> update( 'products_history', [ 'updated' => 0 ], [ 'AND' => [
        'product_id' => $product_id,
        'campaign_id' => $campaign_id,
        'ad_group_id' => $ad_group_id,
        'date_add' => $date
      ] ] );
      $history_30_rows++;
    }

    $aggregate_rows = \controls\Cron::rebuild_products_temp_for_client( $client_id );

    echo json_encode( [
      'status' => 'ok',
      'client_id' => $client_id,
      'date' => $date,
      'processed' => $processed,
      'skipped' => $skipped,
      'history_30_products' => $history_30_rows,
      'products_aggregate_rows' => $aggregate_rows
    ] );
    exit;
  }

  static private function rebuild_products_temp_for_client( $client_id )
  {
    return \controls\Cron::rebuild_products_temp_for_client( $client_id );
  }

  static private function normalize_number( $value )
  {
    if ( $value === null || $value === '' )
    {
      return 0.0;
    }

    if ( is_int( $value ) || is_float( $value ) )
    {
      return (float) $value;
    }

    $value = trim( (string) $value );
    if ( $value === '' )
    {
      return 0.0;
    }

    $value = preg_replace( '/[^\d,.\-]/', '', $value );
    if ( $value === '' || $value === '-' || $value === ',' || $value === '.' )
    {
      return 0.0;
    }

    $has_comma = strpos( $value, ',' ) !== false;
    $has_dot = strpos( $value, '.' ) !== false;

    if ( $has_comma && $has_dot )
    {
      $last_comma = strrpos( $value, ',' );
      $last_dot = strrpos( $value, '.' );

      if ( $last_comma > $last_dot )
      {
        $value = str_replace( '.', '', $value );
        $value = str_replace( ',', '.', $value );
      }
      else
      {
        $value = str_replace( ',', '', $value );
      }
    }
    else
    {
      $value = str_replace( ',', '.', $value );
    }

    return (float) $value;
  }
}

