<?php

declare(strict_types=1);

namespace FecaMailshots\Infrastructure;

use FecaMailshots\Application\SourceMetadataProvider;

final class DatabaseSourceMetadataProvider implements SourceMetadataProvider
{
    private DatabaseRouter $router;

    /** @var array<string, list<string>> */
    private array $builtInFields;
    /** @var array<string, list<string>> */
    private array $sourceFieldsCache = [];

    /** @var array<string, array{left:string,right:string}> */
    private array $joinMap;

    public function __construct(DatabaseRouter $router)
    {
        $this->router = $router;
        $this->builtInFields = [
            'contacts' => ['id', 'account_id', 'first_name', 'last_name', 'contact_email_1', 'contact_email_2', 'is_contact_1', 'is_contact_2', 'is_fen_1', 'is_fen_2', 'is_public_contact'],
            'accounts' => ['id', 'name', 'account_type_id'],
            'renewals' => ['id', 'account_id', 'status', 'selected', 'email'],
            'grants' => ['id', 'account_id', 'name', 'amount', 'applied_for', 'total_cost', 'grant_date', 'notes'],
        ];

        $this->joinMap = [
            'contacts|accounts' => ['left' => 'contacts.account_id', 'right' => 'accounts.id'],
            'accounts|contacts' => ['left' => 'accounts.id', 'right' => 'contacts.account_id'],
            'renewals|accounts' => ['left' => 'renewals.account_id', 'right' => 'accounts.id'],
            'accounts|renewals' => ['left' => 'accounts.id', 'right' => 'renewals.account_id'],
            'renewals|contacts' => ['left' => 'renewals.account_id', 'right' => 'contacts.account_id'],
            'contacts|renewals' => ['left' => 'contacts.account_id', 'right' => 'renewals.account_id'],
            'grants|accounts' => ['left' => 'grants.account_id', 'right' => 'accounts.id'],
            'accounts|grants' => ['left' => 'accounts.id', 'right' => 'grants.account_id'],
        ];
    }

    public function sourceExists(string $source): bool
    {
        if (isset($this->builtInFields[$source])) {
            return true;
        }

        if (!str_contains($source, '.')) {
            return false;
        }

        [$schema, $table] = explode('.', $source, 2);
        $show = 'SHOW TABLES FROM `' . str_replace('`', '``', $schema) . '` LIKE ?';
        $stmt = $this->router->membersPdo()->prepare($show);
        $stmt->execute([$table]);
        return (bool) $stmt->fetchColumn();
    }

    public function sourceFields(string $source): array
    {
        if (isset($this->builtInFields[$source])) {
            $fields = $this->loadFieldsFromTable($source);
            if ($source === 'accounts') {
                foreach (['type', 'public_location', 'account_sector'] as $virtualField) {
                    if (!in_array($virtualField, $fields, true)) {
                        $fields[] = $virtualField;
                    }
                }
            }
            return $fields;
        }

        if (!str_contains($source, '.')) {
            return [];
        }

        [$schema, $table] = explode('.', $source, 2);
        $sql = 'SHOW COLUMNS FROM `' . str_replace('`', '``', $schema) . '`.`' . str_replace('`', '``', $table) . '`';
        $rows = $this->router->membersPdo()->query($sql)->fetchAll(\PDO::FETCH_ASSOC);
        return array_values(array_filter(array_map(static fn(array $row): string => (string) ($row['Field'] ?? ''), $rows), static fn(string $v): bool => $v !== ''));
    }

    public function hasEmailField(string $source): bool
    {
        $fields = $this->sourceFields($source);
        $lower = array_map('strtolower', $fields);
        return in_array('email', $lower, true);
    }

    public function joinPath(string $left, string $right): ?array
    {
        return $this->joinMap[$left . '|' . $right] ?? null;
    }

    public function allKnownSources(): array
    {
        return ['contacts', 'accounts', 'renewals', 'grants'];
    }

    /** @return list<string> */
    private function loadFieldsFromTable(string $table): array
    {
        $table = trim($table);
        if ($table === '') {
            return [];
        }
        if (isset($this->sourceFieldsCache[$table])) {
            return $this->sourceFieldsCache[$table];
        }

        $sql = 'SHOW COLUMNS FROM `' . str_replace('`', '``', $table) . '`';
        $rows = $this->router->membersPdo()->query($sql)->fetchAll(\PDO::FETCH_ASSOC);
        $fields = array_values(array_filter(
            array_map(static fn(array $row): string => trim((string) ($row['Field'] ?? '')), $rows),
            static fn(string $v): bool => $v !== ''
        ));
        if ($fields === []) {
            throw new \RuntimeException('Source "' . $table . '" has no readable columns.');
        }
        $this->sourceFieldsCache[$table] = $fields;
        return $fields;
    }
}
