<?php
namespace services;

class FacebookAdsApi
{
  private $access_token;
  private $api_version;

  public function __construct( $access_token = '', $api_version = 'v25.0' )
  {
    $this -> access_token = trim( (string) $access_token );
    $this -> api_version = trim( (string) $api_version ) ?: 'v25.0';
  }

  public function is_configured()
  {
    return $this -> access_token !== '';
  }

  public function get_api_version()
  {
    return $this -> api_version;
  }

  public static function get_setting( $key )
  {
    global $mdb;
    return $mdb -> get( 'settings', 'setting_value', [ 'setting_key' => $key ] );
  }

  public static function set_setting( $key, $value )
  {
    global $mdb;

    if ( $mdb -> count( 'settings', [ 'setting_key' => $key ] ) )
    {
      $mdb -> update( 'settings', [ 'setting_value' => $value ], [ 'setting_key' => $key ] );
    }
    else
    {
      $mdb -> insert( 'settings', [ 'setting_key' => $key, 'setting_value' => $value ] );
    }

    if ( $key === 'facebook_ads_last_error' )
    {
      $error_at = null;
      if ( $value !== null && trim( (string) $value ) !== '' )
      {
        $error_at = date( 'Y-m-d H:i:s' );
      }

      if ( $mdb -> count( 'settings', [ 'setting_key' => 'facebook_ads_last_error_at' ] ) )
      {
        $mdb -> update( 'settings', [ 'setting_value' => $error_at ], [ 'setting_key' => 'facebook_ads_last_error_at' ] );
      }
      else
      {
        $mdb -> insert( 'settings', [ 'setting_key' => 'facebook_ads_last_error_at', 'setting_value' => $error_at ] );
      }
    }
  }

  public static function normalize_ad_account_id( $account_id )
  {
    $account_id = trim( (string) $account_id );
    if ( $account_id === '' )
    {
      return null;
    }

    if ( stripos( $account_id, 'act_' ) === 0 )
    {
      $digits = preg_replace( '/\D+/', '', substr( $account_id, 4 ) );
      return $digits !== '' ? 'act_' . $digits : null;
    }

    $digits = preg_replace( '/\D+/', '', $account_id );
    if ( $digits === '' )
    {
      return null;
    }

    return 'act_' . $digits;
  }

  public function fetch_active_insights_last_days( $account_id, $days = 30, $active_only = true )
  {
    $days = max( 1, min( 90, (int) $days ) );
    $since = date( 'Y-m-d', strtotime( '-' . ( $days - 1 ) . ' days' ) );
    $until = date( 'Y-m-d' );

    return $this -> fetch_active_insights_range( $account_id, $since, $until, $active_only, $days );
  }

  public function fetch_active_insights_for_date( $account_id, $date, $active_only = true )
  {
    $timestamp = strtotime( (string) $date );
    if ( !$timestamp )
    {
      self::set_setting( 'facebook_ads_last_error', 'Niepoprawna data dla synchronizacji Facebook Ads.' );
      return false;
    }

    $sync_date = date( 'Y-m-d', $timestamp );
    return $this -> fetch_active_insights_range( $account_id, $sync_date, $sync_date, $active_only, 1 );
  }

  public function fetch_active_insights_for_range( $account_id, $since, $until, $active_only = true )
  {
    $since_ts = strtotime( (string) $since );
    $until_ts = strtotime( (string) $until );
    if ( !$since_ts || !$until_ts )
    {
      self::set_setting( 'facebook_ads_last_error', 'Niepoprawny zakres dat dla synchronizacji Facebook Ads.' );
      return false;
    }

    $days = (int) floor( ( $until_ts - $since_ts ) / 86400 ) + 1;
    return $this -> fetch_active_insights_range( $account_id, date( 'Y-m-d', $since_ts ), date( 'Y-m-d', $until_ts ), $active_only, $days, true );
  }

  public function fetch_campaigns_all_time( $account_id, $active_only = true )
  {
    $account_id = self::normalize_ad_account_id( $account_id );
    if ( !$account_id )
    {
      return false;
    }

    if ( !$this -> is_configured() )
    {
      return false;
    }

    $base_url = 'https://graph.facebook.com/' . rawurlencode( $this -> api_version ) . '/' . rawurlencode( $account_id ) . '/insights';

    $params = [
      'access_token' => $this -> access_token,
      'level' => 'campaign',
      'fields' => 'campaign_id,spend,action_values,purchase_roas',
      'date_preset' => 'maximum',
      'limit' => 500
    ];

    if ( $active_only )
    {
      $params['filtering'] = json_encode( [
        [
          'field' => 'campaign.effective_status',
          'operator' => 'IN',
          'value' => [ 'ACTIVE' ]
        ]
      ] );
    }

    $rows = $this -> fetch_all_pages( $base_url, $params );
    if ( $rows === false )
    {
      return false;
    }

    $purchase_action_types = [
      'purchase', 'omni_purchase', 'offsite_conversion.fb_pixel_purchase',
      'web_in_store_purchase', 'onsite_conversion.purchase', 'app_custom_event.fb_mobile_purchase'
    ];

    $campaigns = [];
    foreach ( $rows as $row )
    {
      $campaign_id = (string) ( $row['campaign_id'] ?? '' );
      if ( $campaign_id === '' )
      {
        continue;
      }

      $spend = (float) ( $row['spend'] ?? 0 );

      // 1. ROAS z purchase_roas (preferowane)
      $roas = 0.0;
      if ( isset( $row['purchase_roas'] ) && is_array( $row['purchase_roas'] ) )
      {
        foreach ( $row['purchase_roas'] as $pr )
        {
          $at = trim( (string) ( $pr['action_type'] ?? '' ) );
          if ( in_array( $at, $purchase_action_types, true ) )
          {
            $roas = round( (float) ( $pr['value'] ?? 0 ) * 100, 6 );
            break;
          }
        }
      }

      // 2. Fallback: oblicz z action_values / spend
      $conversion_value = 0.0;
      if ( isset( $row['action_values'] ) && is_array( $row['action_values'] ) )
      {
        foreach ( $row['action_values'] as $action )
        {
          $at = trim( (string) ( $action['action_type'] ?? '' ) );
          if ( in_array( $at, $purchase_action_types, true ) )
          {
            $conversion_value += (float) ( $action['value'] ?? 0 );
            break;
          }
        }
      }

      if ( $roas <= 0 && $spend > 0 && $conversion_value > 0 )
      {
        $roas = round( ( $conversion_value / $spend ) * 100, 6 );
      }

      $campaigns[ $campaign_id ] = [
        'campaign_id' => $campaign_id,
        'spend_all_time' => $spend,
        'conversion_value_all_time' => $conversion_value,
        'roas_all_time' => $roas
      ];
    }

    return $campaigns;
  }

  private function fetch_active_insights_range( $account_id, $since, $until, $active_only = true, $days = 0, $aggregate = false )
  {
    $account_id = self::normalize_ad_account_id( $account_id );
    if ( !$account_id )
    {
      self::set_setting( 'facebook_ads_last_error', 'Niepoprawne Facebook Ads Account ID.' );
      return false;
    }

    if ( !$this -> is_configured() )
    {
      self::set_setting( 'facebook_ads_last_error', 'Brak tokena Facebook Ads API.' );
      return false;
    }

    $since_ts = strtotime( (string) $since );
    $until_ts = strtotime( (string) $until );
    if ( !$since_ts || !$until_ts || $since_ts > $until_ts )
    {
      self::set_setting( 'facebook_ads_last_error', 'Niepoprawny zakres dat Facebook Ads.' );
      return false;
    }

    $since = date( 'Y-m-d', $since_ts );
    $until = date( 'Y-m-d', $until_ts );
    if ( (int) $days <= 0 )
    {
      $days = (int) floor( ( $until_ts - $since_ts ) / 86400 ) + 1;
    }
    $days = max( 1, min( 90, (int) $days ) );

    $base_url = 'https://graph.facebook.com/' . rawurlencode( $this -> api_version ) . '/' . rawurlencode( $account_id ) . '/insights';

    $levels = [
      'campaign' => [
        'fields' => 'account_id,campaign_id,campaign_name,spend,impressions,clicks,ctr,cpc,action_values,purchase_roas,date_start,date_stop',
        'filtering_field' => 'campaign.effective_status'
      ],
      'adset' => [
        'fields' => 'account_id,campaign_id,campaign_name,adset_id,adset_name,spend,impressions,clicks,ctr,cpc,action_values,purchase_roas,date_start,date_stop',
        'filtering_field' => 'adset.effective_status'
      ],
      'ad' => [
        'fields' => 'account_id,campaign_id,campaign_name,adset_id,adset_name,ad_id,ad_name,spend,impressions,clicks,ctr,cpc,action_values,purchase_roas,date_start,date_stop',
        'filtering_field' => 'ad.effective_status'
      ]
    ];

    $result = [
      'meta' => [
        'account_id' => $account_id,
        'api_version' => $this -> api_version,
        'days' => $days,
        'since' => $since,
        'until' => $until,
        'generated_at' => date( 'c' )
      ],
      'campaign' => [],
      'adset' => [],
      'ad' => []
    ];

    foreach ( $levels as $level => $cfg )
    {
      $params = [
        'access_token' => $this -> access_token,
        'level' => $level,
        'fields' => $cfg['fields'],
        'time_range' => json_encode( [ 'since' => $since, 'until' => $until ] ),
        'limit' => 500
      ];

      if ( !$aggregate )
      {
        $params['time_increment'] = 1;
      }

      if ( $active_only )
      {
        $params['filtering'] = json_encode( [
          [
            'field' => $cfg['filtering_field'],
            'operator' => 'IN',
            'value' => [ 'ACTIVE' ]
          ]
        ] );
      }

      $rows = $this -> fetch_all_pages( $base_url, $params );
      if ( $rows === false )
      {
        return false;
      }

      $result[ $level ] = $rows;
    }

    self::set_setting( 'facebook_ads_last_error', null );
    return $result;
  }

  private function fetch_all_pages( $url, $params = null )
  {
    $all_rows = [];
    $next_url = $url;
    $next_params = $params;

    while ( $next_url )
    {
      $payload = $this -> request_json( $next_url, $next_params );
      if ( $payload === false )
      {
        return false;
      }

      if ( isset( $payload['data'] ) && is_array( $payload['data'] ) )
      {
        foreach ( $payload['data'] as $row )
        {
          $all_rows[] = $row;
        }
      }

      $next_url = '';
      $next_params = null;

      if ( isset( $payload['paging']['next'] ) && is_string( $payload['paging']['next'] ) )
      {
        $next_url = $payload['paging']['next'];
      }
    }

    return $all_rows;
  }

  private function request_json( $url, $params = null )
  {
    if ( is_array( $params ) )
    {
      $query = http_build_query( $params );
      $url .= ( strpos( $url, '?' ) === false ? '?' : '&' ) . $query;
    }

    $ch = curl_init( $url );
    curl_setopt_array( $ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_SSL_VERIFYPEER => true,
      CURLOPT_CONNECTTIMEOUT => 20,
      CURLOPT_TIMEOUT => 120
    ] );

    $response = curl_exec( $ch );
    $http_code = (int) curl_getinfo( $ch, CURLINFO_HTTP_CODE );
    $curl_error = curl_error( $ch );
    curl_close( $ch );

    if ( $response === false )
    {
      self::set_setting( 'facebook_ads_last_error', 'cURL error: ' . $curl_error );
      return false;
    }

    $decoded = json_decode( (string) $response, true );
    if ( !is_array( $decoded ) )
    {
      self::set_setting( 'facebook_ads_last_error', 'Niepoprawny JSON odpowiedzi Meta API. HTTP ' . $http_code );
      return false;
    }

    if ( isset( $decoded['error'] ) )
    {
      $message = (string) ( $decoded['error']['message'] ?? 'Nieznany blad Meta API' );
      $code = (string) ( $decoded['error']['code'] ?? '' );
      $subcode = (string) ( $decoded['error']['error_subcode'] ?? '' );
      self::set_setting( 'facebook_ads_last_error', 'Meta API: ' . $message . ' (code: ' . $code . ', subcode: ' . $subcode . ')' );
      return false;
    }

    if ( $http_code >= 400 )
    {
      self::set_setting( 'facebook_ads_last_error', 'Meta API HTTP ' . $http_code . ': ' . substr( (string) $response, 0, 1000 ) );
      return false;
    }

    return $decoded;
  }
}
