<?php
declare(strict_types=1);

namespace App\Services;

use App\Core\Auth;
use App\Core\Database;
use App\Industry\Catalog;
use App\Models\AuditLog;
use App\Models\User;
use RuntimeException;

final class IndustryService
{
    public static function apply(string $code): void
    {
        if (!Catalog::isValid($code)) {
            throw new RuntimeException('Pick setup A, B, C, or D.');
        }
        $tenantId = Auth::instance()->tenantId();
        if (!$tenantId) {
            throw new RuntimeException('No company in session.');
        }
        $template = Catalog::get($code);
        $db = Database::instance();
        $pdo = $db->pdo();
        $pdo->beginTransaction();
        try {
            $oldStages = $db->fetchAll(
                'SELECT * FROM pipeline_stages WHERE tenant_id = :tid AND is_active = 1 ORDER BY sort_order, id',
                ['tid' => $tenantId]
            );
            $db->query(
                'UPDATE pipeline_stages SET is_active = 0 WHERE tenant_id = :tid',
                ['tid' => $tenantId]
            );
            $newIds = [];
            $sort = 1;
            foreach ($template['stages'] as $stage) {
                $db->query(
                    'INSERT INTO pipeline_stages (tenant_id, name, code, sort_order, probability, is_won, is_lost, is_active)
                     VALUES (:tid, :name, :code, :sort, :prob, :won, :lost, 1)',
                    [
                        'tid' => $tenantId,
                        'name' => $stage['name'],
                        'code' => $stage['code'],
                        'sort' => $sort,
                        'prob' => (int) $stage['probability'],
                        'won' => (int) $stage['won'],
                        'lost' => (int) $stage['lost'],
                    ]
                );
                $newIds[] = [
                    'id' => (int) $db->lastId(),
                    'won' => (int) $stage['won'],
                    'lost' => (int) $stage['lost'],
                ];
                $sort++;
            }
            $map = self::stageMap($oldStages, $newIds);
            foreach ($map as $oldId => $newId) {
                $db->query(
                    'UPDATE opportunities SET stage_id = :nid WHERE tenant_id = :tid AND stage_id = :oid',
                    ['nid' => $newId, 'tid' => $tenantId, 'oid' => $oldId]
                );
            }
            $db->query(
                'UPDATE tenants SET industry_code = :code WHERE id = :tid',
                ['code' => $code, 'tid' => $tenantId]
            );
            $pdo->commit();
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $e;
        }
        $userId = (int) Auth::instance()->id();
        $tenant = User::tenantById($userId, $tenantId);
        if ($tenant) {
            Auth::instance()->setTenant($tenant, Auth::instance()->roles());
        }
        AuditLog::write('industry.apply', 'tenant', $tenantId, ['code' => $code]);
        SetupService::onIndustryChanged($code);
    }

    /**
     * @param list<array<string,mixed>> $old
     * @param list<array{id:int,won:int,lost:int}> $new
     * @return array<int,int>
     */
    public static function stageMap(array $old, array $new): array
    {
        $newWon = [];
        $newLost = [];
        $newOpen = [];
        foreach ($new as $s) {
            if ($s['lost'] === 1) {
                $newLost[] = $s['id'];
            } elseif ($s['won'] === 1) {
                $newWon[] = $s['id'];
            } else {
                $newOpen[] = $s['id'];
            }
        }
        $oldOpen = [];
        foreach ($old as $s) {
            if ((int) $s['is_lost'] !== 1 && (int) $s['is_won'] !== 1) {
                $oldOpen[] = (int) $s['id'];
            }
        }
        $map = [];
        foreach ($old as $s) {
            $oid = (int) $s['id'];
            if ((int) $s['is_lost'] === 1) {
                $map[$oid] = $newLost[0] ?? ($newOpen[0] ?? $new[0]['id']);
                continue;
            }
            if ((int) $s['is_won'] === 1) {
                $map[$oid] = $newWon[0] ?? ($newOpen[0] ?? $new[0]['id']);
                continue;
            }
            $idx = array_search($oid, $oldOpen, true);
            if ($idx === false || $newOpen === []) {
                $map[$oid] = $newOpen[0] ?? $new[0]['id'];
                continue;
            }
            $ratio = count($oldOpen) > 1 ? $idx / (count($oldOpen) - 1) : 0;
            $ni = (int) round($ratio * (count($newOpen) - 1));
            $map[$oid] = $newOpen[$ni] ?? $newOpen[0];
        }
        return $map;
    }

    /** @param list<array<string,mixed>> $fields */
    public static function sanitizeAttrs(array $input, array $fields): array
    {
        $out = [];
        foreach ($fields as $field) {
            $key = (string) $field['key'];
            $type = (string) $field['type'];
            $raw = $input[$key] ?? '';
            if (is_array($raw)) {
                continue;
            }
            $value = trim((string) $raw);
            if ($value === '') {
                continue;
            }
            if ($type === 'number' || $type === 'money') {
                if (!is_numeric($value)) {
                    continue;
                }
                $out[$key] = $type === 'money' ? round((float) $value, 2) : $value + 0;
                continue;
            }
            if ($type === 'date') {
                $ts = strtotime($value);
                if ($ts === false) {
                    continue;
                }
                $out[$key] = date('Y-m-d', $ts);
                continue;
            }
            if ($type === 'select') {
                $options = $field['options'] ?? [];
                if (!array_key_exists($value, $options)) {
                    continue;
                }
                $out[$key] = $value;
                continue;
            }
            $out[$key] = mb_substr($value, 0, 500);
        }
        return $out;
    }

    public static function decodeAttrs(mixed $raw): array
    {
        if (is_array($raw)) {
            return $raw;
        }
        if (!is_string($raw) || $raw === '') {
            return [];
        }
        $decoded = json_decode($raw, true);
        return is_array($decoded) ? $decoded : [];
    }
}
