<?php
declare(strict_types=1);

require_once __DIR__ . '/helpers.php';
require_once __DIR__ . '/motorista_metricas_corridas_helpers.php';

const CORRIDA_OFERTA_SEGUNDOS = 35;
const CORRIDA_OFERTA_MOTORISTAS_POR_RODADA = 15;
const CORRIDA_OFERTA_MAX_RODADAS = 2;
const CORRIDA_LISTA_RAIO_KM = 5.0;

function corrida_table_exists(PDO $pdo, string $table): bool
{
    static $cache = [];
    if (array_key_exists($table, $cache)) {
        return $cache[$table];
    }

    $stmt = $pdo->prepare(
        'SELECT COUNT(*)
         FROM INFORMATION_SCHEMA.TABLES
         WHERE TABLE_SCHEMA = DATABASE()
           AND TABLE_NAME = ?'
    );
    $stmt->execute([$table]);
    $cache[$table] = (int) $stmt->fetchColumn() > 0;

    return $cache[$table];
}

function corrida_schema_column_exists(PDO $pdo, string $table, string $column): bool
{
    static $cache = [];
    $key = $table . '.' . $column;
    if (array_key_exists($key, $cache)) {
        return $cache[$key];
    }

    $stmt = $pdo->prepare(
        'SELECT COUNT(*)
         FROM INFORMATION_SCHEMA.COLUMNS
         WHERE TABLE_SCHEMA = DATABASE()
           AND TABLE_NAME = ?
           AND COLUMN_NAME = ?'
    );
    $stmt->execute([$table, $column]);
    $cache[$key] = (int) $stmt->fetchColumn() > 0;

    return $cache[$key];
}

function corrida_ofertas_ativas(PDO $pdo): bool
{
    return corrida_table_exists($pdo, 'corrida_ofertas')
        && corrida_schema_column_exists($pdo, 'corridas', 'oferta_lista_liberada');
}

function corrida_pagamento_liberado_para_oferta(array $corrida): bool
{
    $forma = strtolower(trim((string) ($corrida['forma_pagamento'] ?? '')));
    $detalhe = strtolower(trim((string) ($corrida['forma_pagamento_detalhe'] ?? '')));
    $online = $detalhe === 'pix_online'
        || $detalhe === 'cartao_credito'
        || (($detalhe === '' || $detalhe === 'online') && $forma === 'online');

    if (!$online) {
        return true;
    }

    return strtolower(trim((string) ($corrida['status_pagamento'] ?? ''))) === 'aprovado';
}

function corrida_evento_registrar(
    PDO $pdo,
    int $idCorrida,
    string $evento,
    ?int $idUsuario = null,
    ?string $tipoUsuario = null,
    ?string $statusAnterior = null,
    ?string $statusNovo = null,
    ?string $motivo = null,
    ?string $observacao = null,
    array $dados = []
): void {
    if ($idCorrida <= 0 || !corrida_table_exists($pdo, 'corrida_eventos')) {
        return;
    }

    $stmt = $pdo->prepare(
        'INSERT INTO corrida_eventos
          (id_corrida, id_usuario, tipo_usuario, evento, status_anterior, status_novo, motivo, observacao, dados_json)
         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'
    );
    $stmt->execute([
        $idCorrida,
        $idUsuario,
        $tipoUsuario,
        $evento,
        $statusAnterior,
        $statusNovo,
        $motivo,
        $observacao,
        $dados ? json_encode($dados, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null,
    ]);
}

function corrida_float(?array $data, string $key): ?float
{
    if (!$data || !isset($data[$key]) || trim((string) $data[$key]) === '') {
        return null;
    }

    return (float) str_replace(',', '.', (string) $data[$key]);
}

function corrida_multiplicador_dinamico(array $corrida): float
{
    $multiplicador = corrida_float($corrida, 'multiplicador_dinamico') ?? 1.0;
    return round(max(1.0, $multiplicador), 2);
}

function corrida_distancia_km(?float $lat1, ?float $lng1, ?float $lat2, ?float $lng2): ?float
{
    if ($lat1 === null || $lng1 === null || $lat2 === null || $lng2 === null) {
        return null;
    }

    $earth = 6371.0;
    $dLat = deg2rad($lat2 - $lat1);
    $dLng = deg2rad($lng2 - $lng1);
    $a = sin($dLat / 2) ** 2
        + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng / 2) ** 2;
    $c = 2 * atan2(sqrt($a), sqrt(max(0.0, 1 - $a)));

    return $earth * $c;
}

function corrida_paradas_buscar(PDO $pdo, int $idCorrida): array
{
    if ($idCorrida <= 0 || !corrida_table_exists($pdo, 'corrida_paradas')) {
        return [];
    }

    $stmt = $pdo->prepare(
        'SELECT id, ordem, lat, lng, endereco, confirmada_em
         FROM corrida_paradas
         WHERE id_corrida = ?
         ORDER BY ordem ASC'
    );
    $stmt->execute([$idCorrida]);

    return array_map(static function (array $row): array {
        return [
            'id' => (int) $row['id'],
            'ordem' => (int) $row['ordem'],
            'lat' => (float) $row['lat'],
            'lng' => (float) $row['lng'],
            'endereco' => $row['endereco'] ?? '',
            'confirmada_em' => $row['confirmada_em'],
        ];
    }, $stmt->fetchAll());
}

function corrida_ofertas_expirar(PDO $pdo, ?int $idCorrida = null): void
{
    if (!corrida_ofertas_ativas($pdo)) {
        return;
    }

    $where = 'co.status = "pendente"
              AND co.expira_em <= NOW()
              AND c.status = "pendente"';
    $params = [];
    if ($idCorrida !== null) {
        $where .= ' AND co.id_corrida = ?';
        $params[] = $idCorrida;
    }

    $idsStmt = $pdo->prepare(
        'SELECT DISTINCT co.id_motorista
         FROM corrida_ofertas co
         INNER JOIN corridas c ON c.id = co.id_corrida
         WHERE ' . $where
    );
    $idsStmt->execute($params);
    $idsMotoristas = array_map('intval', $idsStmt->fetchAll(PDO::FETCH_COLUMN));

    $sql = 'UPDATE corrida_ofertas co
            INNER JOIN corridas c ON c.id = co.id_corrida
            SET co.status = "expirada", co.respondido_em = NOW()
            WHERE ' . $where;
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    if ($stmt->rowCount() > 0) {
        motorista_metricas_corridas_recalcular_varios($pdo, $idsMotoristas);
    }
}

function corrida_liberar_lista(PDO $pdo, int $idCorrida): void
{
    if (!corrida_schema_column_exists($pdo, 'corridas', 'oferta_lista_liberada')) {
        return;
    }

    $stmt = $pdo->prepare(
        'UPDATE corridas
         SET oferta_lista_liberada = 1, oferta_lista_em = COALESCE(oferta_lista_em, NOW())
         WHERE id = ? AND status = "pendente"'
    );
    $stmt->execute([$idCorrida]);
}

function corrida_motorista_filtros(PDO $pdo, int $idMotorista): array
{
    if ($idMotorista <= 0 || !corrida_table_exists($pdo, 'motorista_filtros_corrida')) {
        return [
            'aceita_dinheiro' => true,
            'aceita_economica' => true,
            'aceita_plus' => true,
            'aceita_pro' => true,
            'destino_ativo' => false,
            'destino_lat' => null,
            'destino_lng' => null,
        ];
    }

    $stmt = $pdo->prepare(
        'SELECT aceita_dinheiro, aceita_economica, aceita_plus, aceita_pro,
                destino_ativo, destino_lat, destino_lng
         FROM motorista_filtros_corrida
         WHERE id_motorista = ?
         LIMIT 1'
    );
    $stmt->execute([$idMotorista]);
    $row = $stmt->fetch();
    if (!$row) {
        return [
            'aceita_dinheiro' => true,
            'aceita_economica' => true,
            'aceita_plus' => true,
            'aceita_pro' => true,
            'destino_ativo' => false,
            'destino_lat' => null,
            'destino_lng' => null,
        ];
    }

    return [
        'aceita_dinheiro' => (int) ($row['aceita_dinheiro'] ?? 1) === 1,
        'aceita_economica' => (int) ($row['aceita_economica'] ?? 1) === 1,
        'aceita_plus' => (int) ($row['aceita_plus'] ?? 1) === 1,
        'aceita_pro' => (int) ($row['aceita_pro'] ?? 1) === 1,
        'destino_ativo' => (int) ($row['destino_ativo'] ?? 0) === 1,
        'destino_lat' => $row['destino_lat'] === null ? null : (float) $row['destino_lat'],
        'destino_lng' => $row['destino_lng'] === null ? null : (float) $row['destino_lng'],
    ];
}

function corrida_motorista_pode_receber_corrida(PDO $pdo, array $motorista, array $corrida): bool
{
    $idMotorista = (int) ($motorista['id'] ?? 0);
    $filtros = corrida_motorista_filtros($pdo, $idMotorista);

    $formaPagamento = (string) ($corrida['forma_pagamento'] ?? 'dinheiro');
    $detalhePagamento = (string) ($corrida['forma_pagamento_detalhe'] ?? '');
    if (($formaPagamento === 'dinheiro' || $detalhePagamento === 'dinheiro_pix') && !$filtros['aceita_dinheiro']) {
        return false;
    }

    $categoria = (string) ($corrida['categoria_veiculo'] ?? 'economica');
    if (!in_array($categoria, ['economica', 'plus', 'pro'], true)) {
        $categoria = 'economica';
    }
    if ($categoria === 'economica' && !$filtros['aceita_economica']) {
        return false;
    }
    if ($categoria === 'plus' && !$filtros['aceita_plus']) {
        return false;
    }
    if ($categoria === 'pro' && !$filtros['aceita_pro']) {
        return false;
    }

    if (!$filtros['destino_ativo']) {
        return true;
    }

    $motoristaLat = corrida_float($motorista, 'ultimo_lat') ?? corrida_float($motorista, 'lat');
    $motoristaLng = corrida_float($motorista, 'ultimo_lng') ?? corrida_float($motorista, 'lng');
    $destinoModoLat = $filtros['destino_lat'];
    $destinoModoLng = $filtros['destino_lng'];
    $corridaDestinoLat = corrida_float($corrida, 'destino_lat');
    $corridaDestinoLng = corrida_float($corrida, 'destino_lng');

    $distanciaAtual = corrida_distancia_km($motoristaLat, $motoristaLng, $destinoModoLat, $destinoModoLng);
    $distanciaDepois = corrida_distancia_km($corridaDestinoLat, $corridaDestinoLng, $destinoModoLat, $destinoModoLng);
    if ($distanciaAtual === null || $distanciaDepois === null) {
        return false;
    }

    return $distanciaDepois < $distanciaAtual;
}

function corrida_notificar_oferta_motorista(PDO $pdo, int $idMotorista, int $idCorrida, int $idOferta): void
{
    if ($idMotorista <= 0 || $idCorrida <= 0 || $idOferta <= 0) {
        return;
    }

    $pushPath = __DIR__ . '/push.php';
    if (!function_exists('push_enviar_chamada_corrida_motorista') && is_file($pushPath)) {
        require_once $pushPath;
    }
    if (!function_exists('push_enviar_chamada_corrida_motorista')) {
        return;
    }

    $corridaPush = corrida_formatar_para_motorista($pdo, $idCorrida, $idOferta);
    if (!$corridaPush) {
        $stmt = $pdo->prepare('SELECT * FROM corridas WHERE id = ? LIMIT 1');
        $stmt->execute([$idCorrida]);
        $corridaPush = $stmt->fetch() ?: [];
    }

    try {
        push_enviar_chamada_corrida_motorista($idMotorista, $idCorrida, $idOferta, $corridaPush);
    } catch (Throwable) {
        // O despacho da corrida nao pode parar por falha temporaria no Firebase.
    }
}

function corrida_oferta_pendente(PDO $pdo, int $idCorrida): ?array
{
    $stmt = $pdo->prepare(
        'SELECT *
         FROM corrida_ofertas
         WHERE id_corrida = ?
           AND status = "pendente"
           AND expira_em > NOW()
         ORDER BY expira_em ASC
         LIMIT 1'
    );
    $stmt->execute([$idCorrida]);
    $row = $stmt->fetch();

    return $row ?: null;
}

function corrida_buscar_motorista_candidato(PDO $pdo, array $corrida, int $rodada): ?array
{
    $origemLat = corrida_float($corrida, 'origem_lat');
    $origemLng = corrida_float($corrida, 'origem_lng');
    if ($origemLat === null || $origemLng === null) {
        return null;
    }

    $categoria = (string) ($corrida['categoria_veiculo'] ?? 'economica');
    if (!in_array($categoria, ['economica', 'plus', 'pro'], true)) {
        $categoria = 'economica';
    }

    $distanciaSql = '(6371 * ACOS(LEAST(1, GREATEST(-1,
        COS(RADIANS(?)) * COS(RADIANS(u.ultimo_lat)) *
        COS(RADIANS(u.ultimo_lng) - RADIANS(?)) +
        SIN(RADIANS(?)) * SIN(RADIANS(u.ultimo_lat))
    ))))';

    $sql = 'SELECT u.id, u.ultimo_lat, u.ultimo_lng, ' . $distanciaSql . ' AS distancia_ate_origem_km
            FROM usuarios u
            INNER JOIN veiculos v
              ON v.id = u.id_veiculo_ativo
             AND v.id_motorista = u.id
             AND v.status = "aprovado"
            LEFT JOIN veiculo_modelos_catalogo vm ON vm.id = v.id_modelo_catalogo
            WHERE u.tipo = "motorista"
              AND u.disponivel = "sim"
              AND u.cnh_status = "aprovado"
              AND u.selfie_status = "aprovado"
              AND u.ultimo_lat IS NOT NULL
              AND u.ultimo_lng IS NOT NULL
              AND u.localizacao_atualizada_em >= DATE_SUB(NOW(), INTERVAL 3 MINUTE)
              AND NOT EXISTS (
                SELECT 1
                FROM corrida_ofertas co
                WHERE co.id_corrida = ?
                  AND co.id_motorista = u.id
                  AND co.rodada = ?
              )
              AND NOT EXISTS (
                SELECT 1
                FROM corrida_ofertas co_bloqueada
                WHERE co_bloqueada.id_corrida = ?
                  AND co_bloqueada.id_motorista = u.id
                  AND co_bloqueada.status IN ("aceita", "recusada", "cancelada")
              )
              AND (
                ? = "economica"
                OR (? = "plus" AND COALESCE(vm.categoria_plus, 0) = 1)
                OR (? = "pro" AND COALESCE(vm.categoria_pro, 0) = 1)
              )
            ORDER BY distancia_ate_origem_km ASC
            LIMIT 80';

    $stmt = $pdo->prepare($sql);
    $stmt->execute([
        $origemLat,
        $origemLng,
        $origemLat,
        (int) $corrida['id'],
        $rodada,
        (int) $corrida['id'],
        $categoria,
        $categoria,
        $categoria,
    ]);
    $rows = $stmt->fetchAll();

    foreach ($rows as $row) {
        if (corrida_motorista_pode_receber_corrida($pdo, $row, $corrida)) {
            return $row;
        }
    }

    return null;
}

function corrida_ofertas_despachar(PDO $pdo, int $idCorrida): ?array
{
    if (!corrida_ofertas_ativas($pdo)) {
        return null;
    }

    corrida_ofertas_expirar($pdo, $idCorrida);

    $corridaStmt = $pdo->prepare(
        'SELECT *
         FROM corridas
         WHERE id = ?
           AND status = "pendente"
         LIMIT 1'
    );
    $corridaStmt->execute([$idCorrida]);
    $corrida = $corridaStmt->fetch();
    if (!$corrida) {
        return null;
    }

    if (!corrida_pagamento_liberado_para_oferta($corrida)) {
        return null;
    }

    if ((int) ($corrida['oferta_lista_liberada'] ?? 0) === 1) {
        return null;
    }

    $pendente = corrida_oferta_pendente($pdo, $idCorrida);
    if ($pendente) {
        return $pendente;
    }

    for ($rodada = 1; $rodada <= CORRIDA_OFERTA_MAX_RODADAS; $rodada++) {
        $countStmt = $pdo->prepare(
            'SELECT COUNT(*)
             FROM corrida_ofertas
             WHERE id_corrida = ?
               AND rodada = ?'
        );
        $countStmt->execute([$idCorrida, $rodada]);
        $totalRodada = (int) $countStmt->fetchColumn();

        if ($totalRodada >= CORRIDA_OFERTA_MOTORISTAS_POR_RODADA) {
            continue;
        }

        $candidato = corrida_buscar_motorista_candidato($pdo, $corrida, $rodada);
        if (!$candidato) {
            continue;
        }

        $insert = $pdo->prepare(
            'INSERT INTO corrida_ofertas
              (id_corrida, id_motorista, rodada, ordem, status, distancia_ate_origem_km, expira_em)
             VALUES (?, ?, ?, ?, "pendente", ?, DATE_ADD(NOW(), INTERVAL ' . CORRIDA_OFERTA_SEGUNDOS . ' SECOND))'
        );
        $insert->execute([
            $idCorrida,
            (int) $candidato['id'],
            $rodada,
            $totalRodada + 1,
            round((float) $candidato['distancia_ate_origem_km'], 3),
        ]);

        $ofertaId = (int) $pdo->lastInsertId();
        motorista_metricas_corridas_recalcular($pdo, (int) $candidato['id']);
        corrida_notificar_oferta_motorista($pdo, (int) $candidato['id'], $idCorrida, $ofertaId);

        $ofertaStmt = $pdo->prepare('SELECT * FROM corrida_ofertas WHERE id = ? LIMIT 1');
        $ofertaStmt->execute([$ofertaId]);

        return $ofertaStmt->fetch() ?: null;
    }

    corrida_liberar_lista($pdo, $idCorrida);
    return null;
}

function corrida_ofertas_processar_pendentes(PDO $pdo, int $limit = 20): void
{
    if (!corrida_ofertas_ativas($pdo)) {
        return;
    }

    corrida_ofertas_expirar($pdo);
    $limit = max(1, min(50, $limit));
    $stmt = $pdo->query(
        'SELECT id
         FROM corridas
         WHERE status = "pendente"
           AND oferta_lista_liberada = 0
         ORDER BY criada_em ASC
         LIMIT ' . $limit
    );

    foreach ($stmt->fetchAll() as $row) {
        corrida_ofertas_despachar($pdo, (int) $row['id']);
    }
}

function corrida_formatar_para_motorista(
    PDO $pdo,
    int $idCorrida,
    ?int $idOferta = null,
    ?array $motoristaLocal = null
): ?array {
    $stmt = $pdo->prepare(
        'SELECT c.*,
                p.nome AS passageiro_nome,
                p.celular AS passageiro_celular,
                p.passageiro_documento_status,
                COALESCE(pc.total_corridas, 0) AS passageiro_total_corridas
         FROM corridas c
         INNER JOIN usuarios p ON p.id = c.id_passageiro
         LEFT JOIN (
            SELECT id_passageiro, COUNT(*) AS total_corridas
            FROM corridas
            WHERE status = "finalizada"
            GROUP BY id_passageiro
         ) pc ON pc.id_passageiro = c.id_passageiro
         WHERE c.id = ?
         LIMIT 1'
    );
    $stmt->execute([$idCorrida]);
    $corrida = $stmt->fetch();
    if (!$corrida) {
        return null;
    }

    $oferta = null;
    if ($idOferta !== null) {
        $ofertaStmt = $pdo->prepare('SELECT * FROM corrida_ofertas WHERE id = ? LIMIT 1');
        $ofertaStmt->execute([$idOferta]);
        $oferta = $ofertaStmt->fetch() ?: null;
    }

    $driverLat = corrida_float($motoristaLocal, 'lat') ?? corrida_float($motoristaLocal, 'latitude');
    $driverLng = corrida_float($motoristaLocal, 'lng') ?? corrida_float($motoristaLocal, 'longitude');
    $origemLat = corrida_float($corrida, 'origem_lat');
    $origemLng = corrida_float($corrida, 'origem_lng');
    $distanciaAteOrigem = corrida_distancia_km($driverLat, $driverLng, $origemLat, $origemLng);
    if ($distanciaAteOrigem === null && $oferta) {
        $distanciaAteOrigem = corrida_float($oferta, 'distancia_ate_origem_km');
    }

    $distanciaCorrida = corrida_float($corrida, 'distancia') ?? 0.0;
    $valor = corrida_float($corrida, 'valor') ?? 0.0;
    $valorBase = corrida_float($corrida, 'valor_base');
    $multiplicadorDinamico = corrida_multiplicador_dinamico($corrida);
    $valorDinamicoAdicional = corrida_float($corrida, 'valor_dinamico_adicional') ?? 0.0;
    $saldoNegativoCobrado = corrida_float($corrida, 'saldo_negativo_cobrado') ?? 0.0;
    $baseMotorista = max(0.0, $valor - $saldoNegativoCobrado);
    $valorMotorista = round($baseMotorista * 0.85, 2);
    $kmTotal = max(0.0, ($distanciaAteOrigem ?? 0.0) + $distanciaCorrida);
    $ganhoPorKm = $kmTotal > 0 ? round($valorMotorista / $kmTotal, 2) : 0.0;

    $corrida['id'] = (int) $corrida['id'];
    $corrida['id_passageiro'] = (int) $corrida['id_passageiro'];
    $corrida['id_motorista'] = $corrida['id_motorista'] === null ? null : (int) $corrida['id_motorista'];
    $corrida['valor'] = $valor;
    $corrida['valor_base'] = $valorBase === null ? null : round($valorBase, 2);
    $corrida['multiplicador_dinamico'] = $multiplicadorDinamico;
    $corrida['valor_dinamico_adicional'] = round(max(0.0, $valorDinamicoAdicional), 2);
    $corrida['tem_dinamica'] = $multiplicadorDinamico > 1.0;
    $corrida['saldo_negativo_cobrado'] = round($saldoNegativoCobrado, 2);
    $corrida['valor_motorista'] = $valorMotorista;
    $corrida['distancia'] = $distanciaCorrida;
    $corrida['distancia_ate_origem_km'] = $distanciaAteOrigem === null ? null : round($distanciaAteOrigem, 2);
    $corrida['distancia_corrida_km'] = round($distanciaCorrida, 2);
    $corrida['km_total_motorista'] = round($kmTotal, 2);
    $corrida['ganho_por_km'] = $ganhoPorKm;
    $corrida['passageiro_total_corridas'] = (int) ($corrida['passageiro_total_corridas'] ?? 0);
    $corrida['passageiro_nota'] = 5.0;
    $corrida['origem_endereco'] = $corrida['origem_endereco'] ?? '';
    $corrida['destino_endereco'] = $corrida['destino_endereco'] ?? '';
    $corrida['paradas'] = corrida_paradas_buscar($pdo, (int) $corrida['id']);
    $corrida['total_paradas'] = count($corrida['paradas']);
    $codigoEmbarque = preg_replace('/\D+/', '', (string) ($corrida['codigo_embarque'] ?? ''));
    if (strlen($codigoEmbarque) < 4) {
        $celular = preg_replace('/\D+/', '', (string) ($corrida['passageiro_celular'] ?? ''));
        $codigoEmbarque = $celular === '' ? '' : str_pad(substr($celular, -4), 4, '0', STR_PAD_LEFT);
    }
    $corrida['codigo_embarque'] = $codigoEmbarque;
    unset($corrida['passageiro_celular']);

    if ($oferta) {
        $corrida['oferta_id'] = (int) $oferta['id'];
        $corrida['oferta_rodada'] = (int) $oferta['rodada'];
        $corrida['oferta_ordem'] = (int) $oferta['ordem'];
        $corrida['oferta_expira_em'] = $oferta['expira_em'];
        $corrida['segundos_restantes'] = max(0, (int) $pdo
            ->query('SELECT TIMESTAMPDIFF(SECOND, NOW(), ' . $pdo->quote((string) $oferta['expira_em']) . ')')
            ->fetchColumn());
    }

    return $corrida;
}
