<?php

/**
 * Invoice Ninja (https://invoiceninja.com).
 *
 * @link https://github.com/invoiceninja/invoiceninja source repository
 *
 * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
 *
 * @license https://www.elastic.co/licensing/elastic-license
 */

namespace App\Import\Transformer\Invoice2Go;

use App\Import\ImportException;
use App\Import\Transformer\BaseTransformer;
use App\Models\Invoice;
use Illuminate\Support\Str;

/**
 * Class InvoiceTransformer.
 */
class InvoiceTransformer extends BaseTransformer
{
    /**
     * @param $line_items_data
     *
     * @return bool|array
     */
    private $is_amount_discount = false;

    public function transform($invoice_data)
    {
        if (!isset($invoice_data['DocumentNumber'])) {
            throw new ImportException('DocumentNumber key not found in this import file.');
        }

        if ($this->hasInvoice($invoice_data['DocumentNumber'])) {
            throw new ImportException('Invoice number already exists');
        }

        $invoiceStatusMap = [
            'unsent' => Invoice::STATUS_DRAFT,
            'sent'   => Invoice::STATUS_SENT,
            'fully_paid' => Invoice::STATUS_PAID,
        ];

        $this->is_amount_discount = $this->getFloat($invoice_data, 'Discount') > 0 ? true : false;

        $transformed = [
            'is_amount_discount' => $this->is_amount_discount,
            'discount' => $this->getFloat($invoice_data, 'Discount'),
            'company_id'  => $this->company->id,
            'number'      => $this->getString($invoice_data, 'DocumentNumber'),
            'public_notes'       => $this->getString($invoice_data, 'Comment'),
            'date'        => isset($invoice_data['DocumentDate']) ? $this->parseDate($invoice_data['DocumentDate']) : null,
            // 'currency_id' => $this->getCurrencyByCode( $invoice_data, 'Currency' ),
            'amount'      => $this->getFloat($invoice_data, 'TotalAmount'),
            'status_id'   => $invoiceStatusMap[$status
                    = strtolower($this->getString($invoice_data, 'DocumentStatus'))] ?? Invoice::STATUS_SENT,
            // 'viewed'      => $status === 'viewed',
            'line_items'  => $this->harvestLineItems($invoice_data),
        ];

        $client_id = null;

        // if($this->hasClient($this->getString($invoice_data, 'Name') || $this->getContact($this->getString($invoice_data, 'EmailRecipient')))) {

        $client_id = $this->getClient($this->getString($invoice_data, 'Name'), $this->getString($invoice_data, 'EmailRecipient'));

        // }

        if ($client_id) {
            $transformed['client_id'] = $client_id;
        }

        $settings = new \stdClass();
        $settings->currency_id = $this->getCurrencyByCode($invoice_data, 'Currency');

        $transformed['client'] = [
            'name'              => $this->getString($invoice_data, 'Name'),
            'address1'          => $this->getString($invoice_data, 'DocumentRecipientAddress'),
            'shipping_address1' => $this->getString($invoice_data, 'ShipAddress'),
            'credit_balance'    => 0,
            'settings'          => $settings,
            'client_hash'       => Str::random(40),
            'contacts'          => [
                [
                    'email' => $this->getString($invoice_data, 'EmailRecipient'),
                ],
            ],
        ];

        $addresses = $this->harvestAddresses($invoice_data);

        $transformed['client'] = array_merge($transformed['client'], $addresses);

        if (! empty($invoice_data['Date Paid'])) {
            $transformed['payments'] = [
                [
                    'date'   => $this->parseDate($invoice_data['DatePaid']),
                    'amount' => $this->getFloat($invoice_data, 'Payments'),
                ],
            ];
        } elseif (isset($invoice_data['AmountPaidAmount']) && isset($invoice_data['DatePaid'])) {
            $transformed['payments'] = [
                [
                    'date'   => $this->parseDate($invoice_data['DatePaid']),
                    'amount' => $this->getFloat($invoice_data, 'AmountPaidAmount'),
                ],
            ];
        } elseif (isset($invoice_data['DocumentStatus']) && $invoice_data['DocumentStatus'] == 'fully_paid') {

            $transformed['payments'] = [
                [
                    'date'   => isset($invoice_data['DatePaid']) ? $this->parseDate($invoice_data['DatePaid']) : ($this->parseDate($invoice_data['DocumentDate']) ?? now()->format('Y-m-d')),
                    'amount' => $this->getFloat($invoice_data, 'TotalAmount'),
                ],
            ];

        }

        return $transformed;
    }


    private function harvestAddresses($invoice_data)
    {
        $address = $invoice_data['DocumentRecipientAddress'];

        $lines = explode("\n", $address);

        $billing_address = [];
        if (count($lines) == 2) {
            $billing_address['address1'] = $lines[0];

            $parts = explode(",", $lines[1]);

            if (count($parts) == 3) {
                $billing_address['city'] = $parts[0];
                $billing_address['state'] = $parts[1];
                $billing_address['postal_code'] = $parts[2];
            }

        }

        $shipaddress = $invoice_data['ShipAddress'] ?? '';
        $shipping_address = [];

        $lines = explode("\n", $shipaddress);

        if (count($lines) == 2) {
            $shipping_address['address1'] = $lines[0];

            $parts = explode(",", $lines[1]);

            if (count($parts) == 3) {
                $shipping_address['shipping_city'] = $parts[0];
                $shipping_address['shipping_state'] = $parts[1];
                $shipping_address['shipping_postal_code'] = $parts[2];
            }


        }

        return array_merge($billing_address, $shipping_address);

    }


    /*

    Sample invoice2go line item
      "code" => "",
      "description" => "",
      "qty" => "1",
      "unit_type" => "parts",
      "withholding_tax_applies" => "false",
      "applied_taxes" => "",
      "unit_price" => "150",
      "discount_percentage" => "50",
      "discount_type" => "percentage", //amount
      "discount_amount" => "0",

    */



    private function harvestLineItems(array $invoice_data): array
    {

        $default_data
            = [
                [
                    'cost'             	  => $this->getFloat($invoice_data, 'TotalAmount'),
                    'quantity'           => 1,
                    'discount'           => $this->getFloat($invoice_data, 'DiscountValue'),
                    'is_amount_discount' => false,
                ],
            ];

        if (!isset($invoice_data['Items'])) {
            return $default_data;
        }

        // Parse the main CSV data
        $processed = $this->parseCsvWithNestedCsv($invoice_data['Items']);

        $line_items = [];

        foreach ($processed as $item) {
            $_item['cost'] = $item['unit_price'];
            $_item['quantity'] = $item['qty'] ?? 1;
            $_item['discount'] = $item['discount_percentage'] > $item['discount_amount'] ? $item['discount_percentage'] : $item['discount_amount'];
            $_item['is_amount_discount'] = $item['discount_type'] == 'percentage' ? false : true;
            $_item['product_key'] = $item['code'] ?? '';
            $_item['notes'] = $item['description'] ?? '';

            $_item = $this->parseTaxes($_item, $item);
            $this->is_amount_discount = $_item['is_amount_discount'];

            $line_items[] = $_item;
            $_item = [];

        }

        return $line_items;
    }

    private function parseTaxes($ninja_item, $i2g_item): array
    {
        if (is_string($i2g_item['applied_taxes'])) {
            return $ninja_item;
        }

        $ninja_item['tax_name1'] = 'Tax';
        $ninja_item['tax_rate1'] = $i2g_item['applied_taxes']['rate'];

        return $ninja_item;

    }


    /**
     * Parse a nested tax field from Invoice2Go CSV.
     *
     * The tax column arrives as an array after placeholder replacement,
     * containing a semicolon-separated string like "rate,name;10,GST".
     * This converts it into an associative array: ['rate' => '10', 'name' => 'GST'].
     *
     * @param array $field
     * @return array|string
     */
    private function parseNestedTaxField(array $field): array|string
    {
        if (empty($field[0]) || !is_string($field[0])) {
            return '';
        }

        $csv = str_getcsv($field[0], ";");

        if (count($csv) < 2 || empty($csv[0]) || empty($csv[1])) {
            return '';
        }

        $keys = explode(",", $csv[0]);
        $values = explode(",", $csv[1]);

        if (count($keys) !== count($values)) {
            return '';
        }

        return array_combine($keys, $values);
    }

    public function parseCsvWithNestedCsv($csvString, $delimiter = ',', $enclosure = '"', $lineEnding = ';')
    {
        // Regular expression to find nested CSVs
        $nestedCsvPattern = '/"([^"]*(?:""[^"]*)*)"/';
        preg_match_all($nestedCsvPattern, $csvString, $matches);

        // Replace nested CSVs with placeholders
        $placeholders = [];
        foreach ($matches[0] as $index => $match) {
            $placeholder = '___PLACEHOLDER_' . $index . '___';
            $placeholders[$placeholder] = $match;
            $csvString = str_replace($match, $placeholder, $csvString);
        }

        // Parse the main CSV
        $rows = explode($lineEnding, $csvString);
        $parsedRows = [];
        foreach ($rows as $row) {
            $parsedRow = str_getcsv($row, $delimiter, $enclosure);
            $parsedRows[] = $parsedRow;
        }

        // Replace placeholders with parsed nested CSVs
        foreach ($parsedRows as &$row) {
            foreach ($row as &$field) {
                if (isset($placeholders[$field])) {
                    $field = str_getcsv($placeholders[$field], $delimiter, $enclosure);
                }
            }
        }


        foreach ($parsedRows as $key => &$row) {

            if ($key == 0) {
                continue;
            }
            
            if (isset($row[5]) && is_array($row[5])) {
                $row[5] = $this->parseNestedTaxField($row[5]);
            }

            if (isset($row[1]) && is_array($row[1])) {
                $row[1] = $row[1][0] ?? '';
            }

            if (count($parsedRows[0]) === count($row)) {
                $row = array_combine($parsedRows[0], $row);
            }
        }

        unset($parsedRows[0]);

        return $parsedRows;
    }


}
