<?php

namespace WPDesk\FakturyKSEF\Xml\Mapper\FA3;

use Exception;
use WPDesk\FakturyKSEF\Api\Repository\DocumentStateRepository;
use WPDesk\FakturyKSEF\Helpers\Invoice;
use WPDesk\FakturyKSEF\Xml\DataMapper;
use WPDesk\FakturyKSEF\Xml\DocumentData\CorrectionDocumentData;
use WPDesk\FakturyKSEF\Xml\DocumentData\DocumentData;

class KorDataMapper extends VatDataMapper {

	private DocumentStateRepository $document_ksef_state_repository;

	public function __construct( DocumentStateRepository $document_ksef_state_repository ) {
		$this->document_ksef_state_repository = $document_ksef_state_repository;
	}

	protected function map_fa( DocumentData $document ): array {
		if ( ! $document instanceof CorrectionDocumentData ) {
			throw new Exception( 'KorDataMapper requires CorrectionDocumentData' );
		}

		$fa = [
			'KodWaluty' => $document->get_currency(),
			'P_1'       => $document->get_date_of_issue(),
			'P_2'       => $document->get_formatted_number(),
		];

		$date_of_sale = $document->get_date_of_sale();
		if ( ! empty( $date_of_sale ) ) {
			$fa['P_6'] = $date_of_sale;
		}

		$correction_items_raw = $document->get_items();
		$primary_zero         = $document->find_primary_product_zero_rate( $correction_items_raw );

		$items_by_name = [];
		foreach ( $correction_items_raw as $item ) {
			$item['vat_type_ksef'] = $document->determine_ksef_vat_type( $item, $primary_zero );

			$key = sanitize_key( $item['name'] );
			if ( isset( $item['before_correction'] ) && (int) $item['before_correction'] === 1 ) {
				$items_by_name[ $key ]['before'] = $item;
			} else {
				$items_by_name[ $key ]['after'] = $item;
			}
		}

		$totals_by_taxes = $this->calculate_correction_totals( $items_by_name );

		$fa = array_merge( $fa, $this->map_taxes_sums( $totals_by_taxes ) );

		$fa['P_15']          = Invoice::string_as_money_no_currency( $document->get_total_gross() );
		$fa['Adnotacje']     = $this->map_annotations( $document, $totals_by_taxes );
		$fa['RodzajFaktury'] = 'KOR';

		$correction_reason       = $document->get_notes() ?: esc_html__( 'Invoice correction', 'ksef-for-flexible-invoices' );
		$fa['PrzyczynaKorekty']  = $correction_reason;
		$fa['DaneFaKorygowanej'] = $this->map_corrected_invoice( $document );

		$fa['FaWiersz'] = $this->map_correction_items( $items_by_name );

		$payment_ns     = [
			'Zaplacono'      => '1',
			'DataZaplaty'    => date( 'Y-m-d' ),
			'FormaPlatnosci' => ( new DataMapper() )->get_payment_method( $document->get_payment_method() ),
		];
		$fa['Platnosc'] = $payment_ns;

		return $fa;
	}

	private function map_corrected_invoice( CorrectionDocumentData $document_data ): array {
		$parent_invoice_issue_date = $document_data->get_corrected_document()->get_date_of_issue();
		if ( ! $parent_invoice_issue_date || $parent_invoice_issue_date <= 0 ) {
			throw new Exception( 'Invalid or missing issue date for the corrected document.' );
		}

		$corrected_invoice_ns = [
			'DataWystFaKorygowanej' => date( 'Y-m-d', (int) $parent_invoice_issue_date ),
			'NrFaKorygowanej'       => $document_data->get_corrected_document()->get_formatted_number(),
		];

		$ksef_ref_num = $this->get_corrected_invoice_ksef_reference_number( $document_data->get_corrected_document()->get_id() );
		if ( $ksef_ref_num ) {
			$corrected_invoice_ns['NrKSeF']              = '1';
			$corrected_invoice_ns['NrKSeFFaKorygowanej'] = $ksef_ref_num;
		} else {
			$corrected_invoice_ns['NrKSeFN'] = '1';
		}

		return $corrected_invoice_ns;
	}

	private function get_corrected_invoice_ksef_reference_number( int $document_id ): ?string {
		$document_state = $this->document_ksef_state_repository->get( $document_id );
		return $document_state->get_ksef_number();
	}

	private function map_correction_items( array $items_by_name ): array {
		$mapped_items = [];
		$line_number  = 1;

		foreach ( $items_by_name as $group ) {
			$item_before = $group['before'] ?? null;
			$item_after  = $group['after'] ?? null;

			if ( ! $item_before || ! $item_after ) {
				continue;
			}

			$clean_before = $this->normalize_item_data( $item_before, true );
			$clean_after  = $this->normalize_item_data( $item_after, false );

			$line_before = [
				'NrWierszaFa' => (string) $line_number++,
				'P_7'         => $clean_before['name'],
			];
			if ( ! empty( $clean_before['unit'] ) ) {
				$line_before['P_8A'] = $clean_before['unit'];
			}
			$line_before['P_8B'] = $clean_before['quantity'];
			$line_before['P_9A'] = $clean_before['net_price'];

			if ( ! empty( $clean_before['discount'] ) ) {
				$discount            = str_replace( ',', '.', trim( $clean_before['discount'] ) );
				$line_before['P_10'] = Invoice::string_as_money_no_currency( (float) $discount );
			}

			$line_before['P_11']      = Invoice::string_as_money_no_currency( $clean_before['total_net'] );
			$line_before['P_12']      = $clean_before['vat_rate'];
			$line_before['StanPrzed'] = '1';
			$mapped_items[]           = $line_before;

			$line_after = [
				'NrWierszaFa' => (string) $line_number++,
				'P_7'         => $clean_after['name'],
			];
			if ( ! empty( $clean_after['unit'] ) ) {
				$line_after['P_8A'] = $clean_after['unit'];
			}
			$line_after['P_8B'] = $clean_after['quantity'];
			$line_after['P_9A'] = $clean_after['net_price'];

			if ( ! empty( $clean_after['discount'] ) ) {
				$discount           = str_replace( ',', '.', trim( $clean_after['discount'] ) );
				$line_after['P_10'] = Invoice::string_as_money_no_currency( (float) $discount );
			}

			$line_after['P_11'] = Invoice::string_as_money_no_currency( $clean_after['total_net'] );
			$line_after['P_12'] = $clean_after['vat_rate'];
			$mapped_items[]     = $line_after;
		}

		return $mapped_items;
	}

	private function calculate_correction_totals( array $items_by_name ): array {
		$totals_by_taxes = [];

		foreach ( $items_by_name as $group ) {
			$item_before = $group['before'] ?? null;
			$item_after  = $group['after'] ?? null;

			if ( ! $item_before || ! $item_after ) {
				continue;
			}

			$clean_before = $this->normalize_item_data( $item_before, true );
			$clean_after  = $this->normalize_item_data( $item_after, false );

			$vat_before = $item_before['vat_type_ksef'] ?? '23';
			$vat_after  = $item_after['vat_type_ksef'] ?? '23';

			if ( ! isset( $totals_by_taxes[ $vat_before ] ) ) {
				$totals_by_taxes[ $vat_before ] = [
					'total_net' => 0.0,
					'total_vat' => 0.0,
				];
			}
			if ( ! isset( $totals_by_taxes[ $vat_after ] ) ) {
				$totals_by_taxes[ $vat_after ] = [
					'total_net' => 0.0,
					'total_vat' => 0.0,
				];
			}

			if ( $vat_before === $vat_after ) {
				$diff_net = (float) $clean_after['total_net'] - (float) $clean_before['total_net'];
				$diff_vat = abs( (float) ( $item_after['vat_sum'] ?? 0.0 ) ) - abs( (float) ( $item_before['vat_sum'] ?? 0.0 ) );

				$totals_by_taxes[ $vat_after ]['total_net'] += $diff_net;
				$totals_by_taxes[ $vat_after ]['total_vat'] += $diff_vat;
			} else {
				$totals_by_taxes[ $vat_before ]['total_net'] -= (float) $clean_before['total_net'];
				$totals_by_taxes[ $vat_before ]['total_vat'] -= abs( (float) ( $item_before['vat_sum'] ?? 0.0 ) );

				$totals_by_taxes[ $vat_after ]['total_net'] += (float) $clean_after['total_net'];
				$totals_by_taxes[ $vat_after ]['total_vat'] += abs( (float) ( $item_after['vat_sum'] ?? 0.0 ) );
			}
		}

		return $totals_by_taxes;
	}

	private function normalize_item_data( array $item, bool $is_before_correction ): array {
		$normalized         = [];
		$normalized['name'] = $item['name'];
		$normalized['unit'] = trim( str_replace( '.', '', $item['unit'] ) );

		if ( $is_before_correction ) {
			$normalized['quantity']  = abs( $item['quantity'] ?? 1 );
			$normalized['net_price'] = abs( $item['net_price'] ?? 0.0 );
			$normalized['total_net'] = abs( $item['net_price_sum'] ?? 0.0 );
		} else {
			$normalized['quantity']  = $item['quantity'] ?? 1;
			$normalized['net_price'] = $item['net_price'] ?? 0.0;
			$normalized['total_net'] = $item['net_price_sum'] ?? 0.0;
		}

		$normalized['vat_rate'] = $item['vat_type_ksef'];

		return $normalized;
	}
}
