<?php

namespace App\Services;

use App\Models\Activity;
use App\Models\Client;
use App\Models\ClientCase;
use App\Models\Contract;
use App\Models\Person\Person;
use Carbon\Carbon;

class EmailTemplateRenderer
{
    /**
     * Render subject and bodies using a simple {{ key }} replacement.
     * Supported entities: client, person, client_case, contract, activity
     *
     * @param  array{subject:string, html?:string|null, text?:string|null}  $template
     * @param  array{client?:Client, person?:Person, client_case?:ClientCase, contract?:Contract, activity?:Activity, extra?:array}  $ctx
     * @return array{subject:string, html?:string, text?:string}
     */
    public function render(array $template, array $ctx): array
    {
        $map = $this->buildMap($ctx);
        $replacer = static function (?string $input) use ($map): ?string {
            if ($input === null) {
                return null;
            }

            return preg_replace_callback('/{{\s*([a-zA-Z0-9_.]+)\s*}}/', function ($m) use ($map) {
                $key = $m[1];

                // body_text is handled separately by applyBodyText(); preserve as literal
                if ($key === 'body_text') {
                    return $m[0];
                }

                $value = data_get($map, $key, '');

                // If the resolved value is an array (e.g. {{ contract.meta }} used directly),
                // return empty string instead of triggering "Array to string conversion".
                if (is_array($value)) {
                    return '';
                }

                return (string) $value;
            }, $input);
        };

        $bodyText = isset($ctx['body_text']) ? (string) $ctx['body_text'] : '';

        return [
            'subject' => $replacer($template['subject']) ?? '',
            'html' => $this->applyBodyText($replacer($template['html'] ?? null) ?? null, $bodyText, html: true),
            'text' => $this->applyBodyText($replacer($template['text'] ?? null) ?? null, $bodyText, html: false),
        ];
    }

    /**
     * Substitute the literal {{body_text}} placeholder with the user-supplied body text.
     * In HTML context the text is HTML-escaped and newlines are converted to <br>.
     * In plain-text context the raw value is used.
     */
    public function applyBodyText(?string $content, string $bodyText, bool $html = true): ?string
    {
        if ($content === null) {
            return null;
        }
        $replacement = $html
            ? nl2br(htmlspecialchars($bodyText, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'))
            : $bodyText;

        return preg_replace('/{{\s*body_text\s*}}/', $replacement, $content);
    }

    /**
     * @param  array{client?:Client, person?:Person, client_case?:ClientCase, contract?:Contract, activity?:Activity, extra?:array}  $ctx
     */
    protected function buildMap(array $ctx): array
    {
        $formatDateEu = static function ($value): string {
            if ($value === null || $value === '') {
                return '';
            }
            try {
                if ($value instanceof \DateTimeInterface) {
                    return Carbon::instance($value)->format('d.m.Y');
                }

                // Accept common formats (Y-m-d, Y-m-d H:i:s, etc.)
                return Carbon::parse((string) $value)->format('d.m.Y');
            } catch (\Throwable $e) {
                return (string) $value;
            }
        };

        $formatMoneyEu = static function ($value): string {
            if ($value === null || $value === '') {
                return '';
            }
            $num = null;
            if (is_numeric($value)) {
                $num = (float) $value;
            } elseif (is_string($value)) {
                // Try to normalize string numbers like "1,234.56" or "1.234,56"
                $normalized = str_replace([' ', '\u{00A0}'], '', $value);
                $normalized = str_replace(['.', ','], ['.', '.'], $normalized);
                $num = is_numeric($normalized) ? (float) $normalized : null;
            }
            if ($num === null) {
                return (string) $value;
            }

            return number_format($num, 2, ',', '.').' €';
        };

        $out = [];
        if (isset($ctx['client'])) {
            $c = $ctx['client'];
            $out['client'] = [
                'id' => data_get($c, 'id'),
                'uuid' => data_get($c, 'uuid'),
                // Expose nested person for {{ client.person.full_name }} etc.
                'person' => [
                    'first_name' => data_get($c, 'person.first_name'),
                    'last_name' => data_get($c, 'person.last_name'),
                    'full_name' => (function ($c) {
                        $fn = (string) data_get($c, 'person.first_name', '');
                        $ln = (string) data_get($c, 'person.last_name', '');
                        $stored = data_get($c, 'person.full_name');

                        return (string) ($stored ?: trim(trim($fn.' '.$ln)));
                    })($c),
                    'email' => data_get($c, 'person.email'),
                    'phone' => data_get($c, 'person.phone'),
                ],
            ];
        }
        if (isset($ctx['person'])) {
            $p = $ctx['person'];
            $out['person'] = [
                'first_name' => data_get($p, 'first_name'),
                'last_name' => data_get($p, 'last_name'),
                'full_name' => trim((data_get($p, 'first_name', '')).' '.(data_get($p, 'last_name', ''))),
                'email' => data_get($p, 'email'),
                'phone' => data_get($p, 'phone'),
            ];
        }
        if (isset($ctx['client_case'])) {
            $c = $ctx['client_case'];
            $out['case'] = [
                'id' => data_get($c, 'id'),
                'uuid' => data_get($c, 'uuid'),
                'reference' => data_get($c, 'reference'),
                // Expose nested person for {{ case.person.full_name }}; prefer direct relation, fallback to client.person
                'person' => [
                    'first_name' => data_get($c, 'person.first_name') ?? data_get($c, 'client.person.first_name'),
                    'last_name' => data_get($c, 'person.last_name') ?? data_get($c, 'client.person.last_name'),
                    'full_name' => (function ($c) {
                        $stored = data_get($c, 'person.full_name') ?? data_get($c, 'client.person.full_name');
                        if ($stored) {
                            return (string) $stored;
                        }
                        $fn = (string) (data_get($c, 'person.first_name') ?? data_get($c, 'client.person.first_name') ?? '');
                        $ln = (string) (data_get($c, 'person.last_name') ?? data_get($c, 'client.person.last_name') ?? '');

                        return trim(trim($fn.' '.$ln));
                    })($c),
                    'email' => data_get($c, 'person.email') ?? data_get($c, 'client.person.email'),
                    'phone' => data_get($c, 'person.phone') ?? data_get($c, 'client.person.phone'),
                ],
            ];
        }
        if (isset($ctx['contract'])) {
            $co = $ctx['contract'];
            $out['contract'] = [
                'id' => data_get($co, 'id'),
                'uuid' => data_get($co, 'uuid'),
                'reference' => data_get($co, 'reference'),
                // Account amounts — sourced from the related Account model
                'account' => [
                    'balance_amount' => $formatMoneyEu(data_get($co, 'account.balance_amount')),
                    'initial_amount' => $formatMoneyEu(data_get($co, 'account.initial_amount')),
                ],
            ];
            $meta = data_get($co, 'meta');
            if (is_string($meta)) {
                $meta = json_decode($meta, true) ?? [];
            }
            if (is_array($meta)) {
                $out['contract']['meta'] = $this->flattenMetaForTemplate($meta);
            }
        }
        if (isset($ctx['activity'])) {
            $a = $ctx['activity'];
            $out['activity'] = [
                'id' => data_get($a, 'id'),
                'note' => data_get($a, 'note'),
                // EU formatted date and amount by default in emails
                'due_date' => $formatDateEu(data_get($a, 'due_date')),
                'amount' => $formatMoneyEu(data_get($a, 'amount')),
                'action' => [
                    'name' => data_get($a, 'action.name'),
                ],
                'decision' => [
                    'name' => data_get($a, 'decision.name'),
                ],
            ];
        }
        if (! empty($ctx['extra']) && is_array($ctx['extra'])) {
            $out['extra'] = $ctx['extra'];
        }
        if (isset($ctx['mail_profile'])) {
            $mp = $ctx['mail_profile'];
            $out['profile'] = [
                'signature' => is_array($mp->signature) ? $mp->signature : [],
            ];
        }

        return $out;
    }

    /**
     * Flatten a contract meta array so every leaf value is accessible by its bare key.
     *
     * Handles three formats stored in the wild:
     *  1. Numeric wrapper:  { "1": { "sklic": "SI00…", "job_days": 1 } }
     *     → { "sklic": "SI00…", "job_days": 1 }
     *  2. Structured entry: { "sklic": { "value": "SI00…", "type": "string" } }
     *     → { "sklic": "SI00…" }
     *  3. Already flat:     { "sklic": "SI00…" }
     *     → { "sklic": "SI00…" }
     */
    private function flattenMetaForTemplate(array $meta): array
    {
        $flat = [];
        foreach ($meta as $key => $item) {
            if (!is_array($item)) {
                // Plain scalar — keep as-is (format 3)
                if (!array_key_exists($key, $flat)) {
                    $flat[$key] = $item;
                }
            } elseif (array_key_exists('value', $item)) {
                // Structured { value, type, title } entry (format 2)
                $flat[$key] = $item['value'];
            } elseif (is_numeric($key)) {
                // Numeric wrapper key — recurse and alias without the prefix (format 1)
                foreach ($this->flattenMetaForTemplate($item) as $nk => $nv) {
                    if (!array_key_exists($nk, $flat)) {
                        $flat[$nk] = $nv;
                    }
                }
            }
            // Non-numeric nested arrays without a 'value' key are silently skipped
        }

        return $flat;
    }
}
