<?php
namespace Domain\Finances;

class FakturowniaImportRepository
{
  private $mdb;
  private $tablesReady = false;

  public function __construct( $mdb = null )
  {
    if ( $mdb )
      $this -> mdb = $mdb;
    else
    {
      global $mdb;
      $this -> mdb = $mdb;
    }
  }

  public function ensureTables()
  {
    if ( $this -> tablesReady )
      return;

    $this -> mdb -> query(
      'CREATE TABLE IF NOT EXISTS `fakturownia_client_mappings` (
        `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
        `external_client_key` VARCHAR(191) NOT NULL,
        `external_name` VARCHAR(255) NOT NULL,
        `crm_client_id` INT UNSIGNED NOT NULL,
        `created_at` DATETIME NOT NULL,
        `updated_at` DATETIME NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uniq_external_client_key` (`external_client_key`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8'
    );

    $this -> mdb -> query(
      'CREATE TABLE IF NOT EXISTS `fakturownia_item_mappings` (
        `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
        `external_item_key` VARCHAR(191) NOT NULL,
        `external_name` VARCHAR(255) NOT NULL,
        `finance_category_id` INT UNSIGNED NOT NULL,
        `created_at` DATETIME NOT NULL,
        `updated_at` DATETIME NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uniq_external_item_key` (`external_item_key`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8'
    );

    $this -> mdb -> query(
      'CREATE TABLE IF NOT EXISTS `fakturownia_skipped_positions` (
        `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
        `external_id` VARCHAR(64) NOT NULL,
        `document_type` VARCHAR(32) NOT NULL,
        `external_item_key` VARCHAR(191) NOT NULL,
        `item_name` VARCHAR(255) NOT NULL,
        `created_at` DATETIME NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uniq_doc_item` (`external_id`, `document_type`, `external_item_key`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8'
    );

    $this -> mdb -> query(
      'CREATE TABLE IF NOT EXISTS `fakturownia_imported_documents` (
        `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
        `external_document_key` VARCHAR(191) NOT NULL,
        `document_type` VARCHAR(32) NOT NULL,
        `external_id` VARCHAR(64) NOT NULL,
        `source_date` DATE NULL,
        `amount` DECIMAL(12,2) NOT NULL DEFAULT 0.00,
        `finance_operation_ids` TEXT NULL,
        `meta_json` LONGTEXT NULL,
        `imported_at` DATETIME NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uniq_external_document_key` (`external_document_key`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8'
    );

    $this -> mdb -> query(
      'CREATE TABLE IF NOT EXISTS `fakturownia_unmapped_queue` (
        `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
        `queue_type` VARCHAR(32) NOT NULL,
        `external_key` VARCHAR(191) NOT NULL,
        `external_name` VARCHAR(255) NOT NULL,
        `payload_json` LONGTEXT NULL,
        `hits` INT UNSIGNED NOT NULL DEFAULT 1,
        `resolved` TINYINT(1) NOT NULL DEFAULT 0,
        `first_seen_at` DATETIME NOT NULL,
        `last_seen_at` DATETIME NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uniq_type_key` (`queue_type`, `external_key`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8'
    );

    $this -> mdb -> query(
      'CREATE TABLE IF NOT EXISTS `fakturownia_import_state` (
        `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
        `state_key` VARCHAR(191) NOT NULL,
        `state_value` LONGTEXT NULL,
        `updated_at` DATETIME NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uniq_state_key` (`state_key`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8'
    );

    $this -> tablesReady = true;
  }

  public function getClientMapping( $externalClientKey )
  {
    $this -> ensureTables();
    return $this -> mdb -> get( 'fakturownia_client_mappings', '*', [
      'external_client_key' => $externalClientKey
    ] );
  }

  public function saveClientMapping( $externalClientKey, $externalName, $crmClientId )
  {
    $this -> ensureTables();

    $current = $this -> getClientMapping( $externalClientKey );
    $now = date( 'Y-m-d H:i:s' );

    if ( $current )
    {
      $this -> mdb -> update( 'fakturownia_client_mappings', [
        'external_name' => $externalName,
        'crm_client_id' => (int)$crmClientId,
        'updated_at' => $now
      ], [ 'id' => (int)$current['id'] ] );
    }
    else
    {
      $this -> mdb -> insert( 'fakturownia_client_mappings', [
        'external_client_key' => $externalClientKey,
        'external_name' => $externalName,
        'crm_client_id' => (int)$crmClientId,
        'created_at' => $now,
        'updated_at' => $now
      ] );
    }

    $this -> resolveQueueItem( 'client', $externalClientKey );
  }

  public function getItemMapping( $externalItemKey )
  {
    $this -> ensureTables();
    return $this -> mdb -> get( 'fakturownia_item_mappings', '*', [
      'external_item_key' => $externalItemKey
    ] );
  }

  public function saveItemMapping( $externalItemKey, $externalName, $financeCategoryId )
  {
    $this -> ensureTables();

    $current = $this -> getItemMapping( $externalItemKey );
    $now = date( 'Y-m-d H:i:s' );

    if ( $current )
    {
      $this -> mdb -> update( 'fakturownia_item_mappings', [
        'external_name' => $externalName,
        'finance_category_id' => (int)$financeCategoryId,
        'updated_at' => $now
      ], [ 'id' => (int)$current['id'] ] );
    }
    else
    {
      $this -> mdb -> insert( 'fakturownia_item_mappings', [
        'external_item_key' => $externalItemKey,
        'external_name' => $externalName,
        'finance_category_id' => (int)$financeCategoryId,
        'created_at' => $now,
        'updated_at' => $now
      ] );
    }

    $this -> resolveQueueItem( 'item', $externalItemKey );
  }

  public function markDocumentPositionSkipped( $externalId, $documentType, $externalItemKey, $itemName )
  {
    $this -> ensureTables();

    $existing = $this -> mdb -> get( 'fakturownia_skipped_positions', 'id', [
      'AND' => [
        'external_id' => (string)$externalId,
        'document_type' => (string)$documentType,
        'external_item_key' => (string)$externalItemKey
      ]
    ] );
    if ( $existing )
      return;

    $this -> mdb -> insert( 'fakturownia_skipped_positions', [
      'external_id' => (string)$externalId,
      'document_type' => (string)$documentType,
      'external_item_key' => (string)$externalItemKey,
      'item_name' => (string)$itemName,
      'created_at' => date( 'Y-m-d H:i:s' )
    ] );
  }

  public function isDocumentPositionSkipped( $externalId, $documentType, $externalItemKey )
  {
    $this -> ensureTables();
    return (bool)$this -> mdb -> has( 'fakturownia_skipped_positions', [
      'AND' => [
        'external_id' => (string)$externalId,
        'document_type' => (string)$documentType,
        'external_item_key' => (string)$externalItemKey
      ]
    ] );
  }

  public function removeOccurrenceFromItemQueue( $externalItemKey, $externalId )
  {
    $this -> ensureTables();

    $row = $this -> mdb -> get( 'fakturownia_unmapped_queue', '*', [
      'AND' => [
        'queue_type' => 'item',
        'external_key' => (string)$externalItemKey
      ]
    ] );
    if ( !$row )
      return;

    $payload = [];
    if ( isset( $row['payload_json'] ) && $row['payload_json'] )
    {
      $decoded = json_decode( $row['payload_json'], true );
      if ( is_array( $decoded ) )
        $payload = $decoded;
    }

    $occurrences = [];
    if ( isset( $payload['occurrences'] ) && is_array( $payload['occurrences'] ) )
    {
      foreach ( $payload['occurrences'] as $occ )
      {
        if ( !is_array( $occ ) )
          continue;
        if ( (string)( $occ['document_id'] ?? '' ) === (string)$externalId )
          continue;
        $occurrences[] = $occ;
      }
    }

    if ( empty( $occurrences ) )
    {
      $this -> mdb -> delete( 'fakturownia_unmapped_queue', [ 'id' => (int)$row['id'] ] );
      return;
    }

    $payload['occurrences'] = $occurrences;
    $this -> mdb -> update( 'fakturownia_unmapped_queue', [
      'payload_json' => json_encode( $payload, JSON_UNESCAPED_UNICODE ),
      'hits' => count( $occurrences ),
      'last_seen_at' => date( 'Y-m-d H:i:s' )
    ], [ 'id' => (int)$row['id'] ] );
  }

  public function isDocumentImported( $externalDocumentKey )
  {
    $this -> ensureTables();
    return (bool)$this -> mdb -> has( 'fakturownia_imported_documents', [
      'external_document_key' => $externalDocumentKey
    ] );
  }

  public function markDocumentImported( $externalDocumentKey, $documentType, $externalId, $sourceDate, $amount, $operationIds, $meta )
  {
    $this -> ensureTables();

    $now = date( 'Y-m-d H:i:s' );
    $data = [
      'external_document_key' => $externalDocumentKey,
      'document_type' => $documentType,
      'external_id' => (string)$externalId,
      'source_date' => $sourceDate,
      'amount' => (float)$amount,
      'finance_operation_ids' => implode( ',', $operationIds ),
      'meta_json' => json_encode( $meta, JSON_UNESCAPED_UNICODE ),
      'imported_at' => $now
    ];

    $existing = $this -> mdb -> get( 'fakturownia_imported_documents', 'id', [
      'external_document_key' => $externalDocumentKey
    ] );

    if ( $existing )
    {
      $this -> mdb -> update( 'fakturownia_imported_documents', $data, [
        'id' => (int)$existing
      ] );
      return;
    }

    $this -> mdb -> insert( 'fakturownia_imported_documents', $data );
  }

  public function queueUnmapped( $queueType, $externalKey, $externalName, $payload )
  {
    $this -> ensureTables();

    $existing = $this -> mdb -> get( 'fakturownia_unmapped_queue', '*', [
      'AND' => [
        'queue_type' => $queueType,
        'external_key' => $externalKey
      ]
    ] );

    $now = date( 'Y-m-d H:i:s' );

    // Dla queue_type='item' agregujemy occurrences per (document_id, item_key).
    // Kazde wystapienie pozycji na innej fakturze to osobny wpis w payload.occurrences,
    // zeby UI mogl pokazac liste i pozwolic na pomijanie per konkretna faktura.
    if ( $queueType === 'item' )
    {
      $occurrence = $payload;
      $payloadToStore = [ 'occurrences' => [ $occurrence ] ];

      if ( $existing )
      {
        $existingPayload = [];
        if ( isset( $existing['payload_json'] ) && $existing['payload_json'] )
        {
          $decoded = json_decode( $existing['payload_json'], true );
          if ( is_array( $decoded ) )
            $existingPayload = $decoded;
        }

        $occurrences = ( isset( $existingPayload['occurrences'] ) && is_array( $existingPayload['occurrences'] ) )
          ? $existingPayload['occurrences']
          : [];

        // Dedup po document_id — powtorny import nadpisuje istniejace wystapienie.
        $occurrences = array_values( array_filter( $occurrences, function( $occ ) use ( $occurrence )
        {
          if ( !is_array( $occ ) )
            return false;
          return (string)( $occ['document_id'] ?? '' ) !== (string)( $occurrence['document_id'] ?? '' );
        } ) );

        $occurrences[] = $occurrence;
        $payloadToStore = [ 'occurrences' => $occurrences ];

        $this -> mdb -> update( 'fakturownia_unmapped_queue', [
          'external_name' => $externalName,
          'payload_json' => json_encode( $payloadToStore, JSON_UNESCAPED_UNICODE ),
          'hits' => count( $occurrences ),
          'resolved' => 0,
          'last_seen_at' => $now
        ], [ 'id' => (int)$existing['id'] ] );
        return;
      }

      $this -> mdb -> insert( 'fakturownia_unmapped_queue', [
        'queue_type' => $queueType,
        'external_key' => $externalKey,
        'external_name' => $externalName,
        'payload_json' => json_encode( $payloadToStore, JSON_UNESCAPED_UNICODE ),
        'hits' => 1,
        'resolved' => 0,
        'first_seen_at' => $now,
        'last_seen_at' => $now
      ] );
      return;
    }

    // Pozostale queue_type (client) — zachowanie bez zmian.
    $payloadJson = json_encode( $payload, JSON_UNESCAPED_UNICODE );

    if ( $existing )
    {
      $this -> mdb -> update( 'fakturownia_unmapped_queue', [
        'external_name' => $externalName,
        'payload_json' => $payloadJson,
        'hits[+]' => 1,
        'resolved' => 0,
        'last_seen_at' => $now
      ], [ 'id' => (int)$existing['id'] ] );
      return;
    }

    $this -> mdb -> insert( 'fakturownia_unmapped_queue', [
      'queue_type' => $queueType,
      'external_key' => $externalKey,
      'external_name' => $externalName,
      'payload_json' => $payloadJson,
      'hits' => 1,
      'resolved' => 0,
      'first_seen_at' => $now,
      'last_seen_at' => $now
    ] );
  }

  public function pendingClientMappings()
  {
    $this -> ensureTables();
    return $this -> mdb -> select( 'fakturownia_unmapped_queue', '*', [
      'AND' => [
        'queue_type' => 'client',
        'resolved' => 0
      ],
      'ORDER' => [ 'last_seen_at' => 'DESC' ]
    ] );
  }

  public function pendingItemMappings()
  {
    $this -> ensureTables();
    return $this -> mdb -> select( 'fakturownia_unmapped_queue', '*', [
      'AND' => [
        'queue_type' => 'item',
        'resolved' => 0,
        'external_key[!]' => 'name:faktura bez pozycji'
      ],
      'ORDER' => [ 'last_seen_at' => 'DESC' ]
    ] );
  }

  public function saveState( $stateKey, $stateValue )
  {
    $this -> ensureTables();

    $existing = $this -> mdb -> get( 'fakturownia_import_state', '*', [
      'state_key' => $stateKey
    ] );
    $now = date( 'Y-m-d H:i:s' );

    if ( $existing )
    {
      $this -> mdb -> update( 'fakturownia_import_state', [
        'state_value' => $stateValue,
        'updated_at' => $now
      ], [ 'id' => (int)$existing['id'] ] );
      return;
    }

    $this -> mdb -> insert( 'fakturownia_import_state', [
      'state_key' => $stateKey,
      'state_value' => $stateValue,
      'updated_at' => $now
    ] );
  }

  public function getState( $stateKey )
  {
    $this -> ensureTables();
    return $this -> mdb -> get( 'fakturownia_import_state', 'state_value', [
      'state_key' => $stateKey
    ] );
  }

  private function resolveQueueItem( $queueType, $externalKey )
  {
    $this -> mdb -> update( 'fakturownia_unmapped_queue', [
      'resolved' => 1,
      'last_seen_at' => date( 'Y-m-d H:i:s' )
    ], [
      'AND' => [
        'queue_type' => $queueType,
        'external_key' => $externalKey
      ]
    ] );
  }
}
