<?php
namespace Domain\Finances;

class FakturowniaApiClient
{
  private $baseUrl;
  private $apiToken;
  private $pageLimit;
  private $timeout;

  public function __construct( $baseUrl, $apiToken, $pageLimit = 100, $timeout = 20 )
  {
    $this -> baseUrl = rtrim( (string)$baseUrl, '/' );
    $this -> apiToken = (string)$apiToken;
    $this -> pageLimit = max( 1, (int)$pageLimit );
    $this -> timeout = max( 5, (int)$timeout );
  }

  public function fetchSalesDocuments( $startDate, $page = 1 )
  {
    // period=more + date_from: Fakturownia filtruje po stronie API po issue_date.
    // Dzieki temu nie pobieramy historycznych faktur i paginacja konczy sie naturalnie.
    $query = $this -> buildDateRangeQuery( $startDate, $page );

    return $this -> requestList( '/invoices.json', $query );
  }

  public function fetchCostDocuments( $startDate, $page = 1 )
  {
    // W Fakturowni "faktura kosztowa" moze byc zapisana jako:
    //  a) zwykla faktura z income=false (widoczna w /invoices.json?income=no, URL /invoices/ID) — primary source
    //  b) koszt w oddzielnym module /costs.json
    //  c) wydatek w /expenses.json (starsze API)
    // Odpytujemy wszystkie trzy i merge'ujemy po id, zeby zadna faktura nie przepadla.
    // Pusta odpowiedz HTTP 200 z ktoregokolwiek endpointu NIE konczy pobierania —
    // idziemy dalej do kolejnych sciezek (to byl bug przed 05-04).
    // period=more + date_from filtruje po stronie API, wiec paginacja konczy sie naturalnie.

    $baseQuery = $this -> buildDateRangeQuery( $startDate, $page );

    $attempts = [
      [ 'path' => '/invoices.json', 'query' => array_merge( $baseQuery, [ 'income' => 'no' ] ) ],
      [ 'path' => '/costs.json',    'query' => $baseQuery ],
      [ 'path' => '/expenses.json', 'query' => $baseQuery ]
    ];

    $merged = [];
    $seenIds = [];
    $hadAnySuccess = false;
    $lastError = null;

    foreach ( $attempts as $attempt )
    {
      try
      {
        $response = $this -> requestList( $attempt['path'], $attempt['query'], true );
      }
      catch ( \Throwable $e )
      {
        $lastError = $e;
        continue;
      }

      if ( !$response['ok'] )
      {
        $lastError = new \RuntimeException( 'Blad HTTP dla ' . $attempt['path'] );
        continue;
      }

      $hadAnySuccess = true;

      foreach ( $response['data'] as $document )
      {
        $id = $this -> extractDocumentId( $document );
        if ( $id === '' )
        {
          $merged[] = $document;
          continue;
        }

        if ( isset( $seenIds[ $id ] ) )
          continue;

        $seenIds[ $id ] = true;
        $merged[] = $document;
      }
    }

    if ( !$hadAnySuccess && $lastError !== null )
      throw new \RuntimeException( 'Nie udalo sie pobrac faktur kosztowych z API Fakturowni: ' . $lastError -> getMessage() );

    return $merged;
  }

  private function buildDateRangeQuery( $startDate, $page )
  {
    $query = [
      'page' => (int)$page,
      'per_page' => $this -> pageLimit
    ];

    if ( is_string( $startDate ) && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $startDate ) )
    {
      $query['period'] = 'more';
      $query['date_from'] = $startDate;
      $query['date_to'] = date( 'Y-m-d' );
    }

    return $query;
  }

  private function extractDocumentId( $document )
  {
    if ( !is_array( $document ) )
      return '';

    if ( isset( $document['invoice'] ) && is_array( $document['invoice'] ) )
      $document = $document['invoice'];

    if ( isset( $document['id'] ) && (string)$document['id'] !== '' )
      return (string)$document['id'];

    return '';
  }

  public function fetchInvoiceDetails( $invoiceId )
  {
    $invoiceId = (int)$invoiceId;
    if ( $invoiceId <= 0 )
      return null;

    $result = $this -> request( '/invoices/' . $invoiceId . '.json', [] );
    if ( $result['http_code'] >= 400 )
      return null;

    $data = json_decode( $result['body'], true );
    return is_array( $data ) ? $data : null;
  }

  private function requestList( $path, $query, $softFail = false )
  {
    $result = $this -> request( $path, $query );

    if ( $result['http_code'] >= 400 )
    {
      if ( $softFail )
        return [ 'ok' => false, 'data' => [] ];

      throw new \RuntimeException( 'Blad API Fakturowni: HTTP ' . $result['http_code'] . ' dla ' . $path );
    }

    $data = json_decode( $result['body'], true );
    if ( !is_array( $data ) )
    {
      if ( $softFail )
        return [ 'ok' => false, 'data' => [] ];

      throw new \RuntimeException( 'API Fakturowni zwrocilo niepoprawny JSON.' );
    }

    $list = $this -> extractList( $data );
    return $softFail ? [ 'ok' => true, 'data' => $list ] : $list;
  }

  protected function request( $path, $query )
  {
    $query['api_token'] = $this -> apiToken;
    $url = $this -> baseUrl . $path . '?' . http_build_query( $query );

    $ch = curl_init( $url );
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
    curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, $this -> timeout );
    curl_setopt( $ch, CURLOPT_TIMEOUT, $this -> timeout );
    curl_setopt( $ch, CURLOPT_HTTPHEADER, [
      'Accept: application/json'
    ] );

    $body = curl_exec( $ch );
    $httpCode = (int)curl_getinfo( $ch, CURLINFO_HTTP_CODE );
    $error = curl_error( $ch );
    curl_close( $ch );

    if ( $body === false )
      throw new \RuntimeException( 'Blad polaczenia z API Fakturowni: ' . $error );

    return [
      'http_code' => $httpCode,
      'body' => $body
    ];
  }

  private function extractList( $data )
  {
    if ( $this -> isList( $data ) )
      return $data;

    $keys = [ 'invoices', 'costs', 'expenses', 'data' ];
    foreach ( $keys as $key )
    {
      if ( isset( $data[ $key ] ) && is_array( $data[ $key ] ) )
      {
        if ( $this -> isList( $data[ $key ] ) )
          return $data[ $key ];
      }
    }

    return [];
  }

  private function isList( $value )
  {
    if ( !is_array( $value ) )
      return false;

    if ( $value === [] )
      return true;

    return array_keys( $value ) === range( 0, count( $value ) - 1 );
  }

}
