<?php
declare(strict_types=1);

/*
|--------------------------------------------------------------------------
| ANALISADOR LOCAL DE PELE - SEM CHAVE E SEM API EXTERNA
|--------------------------------------------------------------------------
| Recebe a foto do Android, salva em uploads, chama o analisador Python local,
| grava os resultados no MySQL e devolve o relatório ao aplicativo.
|--------------------------------------------------------------------------
*/

error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/erro-analise-pele.log');
ini_set('memory_limit', '256M');
set_time_limit(180);

header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');

function responderJson(
    bool $sucesso,
    string $mensagem,
    int $statusHttp = 200,
    array $extras = []
): void {
    http_response_code($statusHttp);

    echo json_encode(
        array_merge(
            [
                'sucesso' => $sucesso,
                'mensagem' => $mensagem
            ],
            $extras
        ),
        JSON_UNESCAPED_UNICODE |
        JSON_UNESCAPED_SLASHES |
        JSON_INVALID_UTF8_SUBSTITUTE
    );

    exit;
}

function apagarArquivoSeguro(?string $arquivo): void
{
    if ($arquivo && is_file($arquivo)) {
        @unlink($arquivo);
    }
}

function valorTexto(array $dados, array $caminho, string $padrao = ''): string
{
    $valor = $dados;

    foreach ($caminho as $chave) {
        if (!is_array($valor) || !array_key_exists($chave, $valor)) {
            return $padrao;
        }

        $valor = $valor[$chave];
    }

    return is_scalar($valor) ? trim((string) $valor) : $padrao;
}

function valorNumero(array $dados, array $caminho): ?float
{
    $valor = $dados;

    foreach ($caminho as $chave) {
        if (!is_array($valor) || !array_key_exists($chave, $valor)) {
            return null;
        }

        $valor = $valor[$chave];
    }

    return is_numeric($valor) ? (float) $valor : null;
}

// -------------------------------------------------------------------------
// BANCO MYSQL
// -------------------------------------------------------------------------

$host = 'localhost';
$user = 'pele';
$pass = 'pele';
$db   = 'pele';

// Python e analisador instalados pelo instalar_analisador.sh.
$pythonExecutavel = '/opt/pele-ai/venv/bin/python';
$scriptAnalisador = '/opt/pele-ai/analisador_local.py';

// Pasta de uploads ao lado deste PHP.
$pastaUploads = __DIR__ . DIRECTORY_SEPARATOR . 'uploads';

// -------------------------------------------------------------------------
// VALIDAÇÕES
// -------------------------------------------------------------------------

if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
    responderJson(
        false,
        'Método inválido. Envie a foto utilizando POST.',
        405
    );
}

$pacienteId = (int) ($_POST['paciente_id'] ?? 0);

if ($pacienteId <= 0) {
    responderJson(
        false,
        'Paciente inválido. Cadastre ou selecione o paciente novamente.',
        422
    );
}

mysqli_report(MYSQLI_REPORT_OFF);

$conexaoPaciente = @new mysqli(
    $host,
    $user,
    $pass,
    $db
);

if ($conexaoPaciente->connect_errno) {
    responderJson(
        false,
        'Erro ao validar o paciente no banco de dados.',
        500
    );
}

$conexaoPaciente->set_charset('utf8mb4');

$stmtPaciente = $conexaoPaciente->prepare("
    SELECT id
    FROM pacientes_pele
    WHERE id = ?
    LIMIT 1
");

if (!$stmtPaciente) {
    $conexaoPaciente->close();

    responderJson(
        false,
        'Erro ao preparar a validação do paciente.',
        500
    );
}

$stmtPaciente->bind_param('i', $pacienteId);
$stmtPaciente->execute();

$pacienteExiste =
    $stmtPaciente
        ->get_result()
        ->fetch_assoc();

$stmtPaciente->close();
$conexaoPaciente->close();

if (!$pacienteExiste) {
    responderJson(
        false,
        'Paciente não encontrado.',
        404
    );
}

if (!isset($_FILES['foto']) || !is_array($_FILES['foto'])) {
    responderJson(
        false,
        'Nenhuma imagem foi recebida. O campo deve se chamar foto.',
        400
    );
}

if (!function_exists('proc_open')) {
    responderJson(
        false,
        'A função proc_open do PHP está desabilitada. Remova proc_open de disable_functions no php.ini.',
        500
    );
}

if (!is_file($pythonExecutavel)) {
    responderJson(
        false,
        'Python local não encontrado. Execute instalar_analisador.sh no servidor.',
        500
    );
}

if (!is_file($scriptAnalisador)) {
    responderJson(
        false,
        'Script analisador_local.py não encontrado em /opt/pele-ai.',
        500
    );
}

// -------------------------------------------------------------------------
// UPLOAD
// -------------------------------------------------------------------------

$foto = $_FILES['foto'];
$erroUpload = (int) ($foto['error'] ?? UPLOAD_ERR_NO_FILE);

if ($erroUpload !== UPLOAD_ERR_OK) {
    responderJson(
        false,
        'Erro no upload da foto. Código: ' . $erroUpload,
        400
    );
}

$temporario = (string) ($foto['tmp_name'] ?? '');
$tamanho = (int) ($foto['size'] ?? 0);

if ($temporario === '' || !is_uploaded_file($temporario)) {
    responderJson(false, 'Upload de foto inválido.', 400);
}

if ($tamanho <= 0 || $tamanho > 10 * 1024 * 1024) {
    responderJson(
        false,
        'A foto está vazia ou ultrapassa 10 MB.',
        413
    );
}

$mime = '';

if (class_exists('finfo')) {
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mimeEncontrado = $finfo->file($temporario);

    if (is_string($mimeEncontrado)) {
        $mime = strtolower(trim($mimeEncontrado));
    }
} elseif (function_exists('mime_content_type')) {
    $mimeEncontrado = mime_content_type($temporario);

    if (is_string($mimeEncontrado)) {
        $mime = strtolower(trim($mimeEncontrado));
    }
}

$formatos = [
    'image/jpeg' => 'jpg',
    'image/png'  => 'png',
    'image/webp' => 'webp'
];

if (!isset($formatos[$mime])) {
    responderJson(
        false,
        'Formato inválido. Envie JPG, PNG ou WEBP.',
        415
    );
}

$dimensoes = @getimagesize($temporario);

if ($dimensoes === false) {
    responderJson(
        false,
        'O arquivo enviado não é uma imagem válida.',
        415
    );
}

$larguraImagem = (int) ($dimensoes[0] ?? 0);
$alturaImagem = (int) ($dimensoes[1] ?? 0);

if ($larguraImagem < 100 || $alturaImagem < 100) {
    responderJson(
        false,
        'A imagem precisa ter pelo menos 100 x 100 pixels.',
        400
    );
}

if (!is_dir($pastaUploads)) {
    @mkdir($pastaUploads, 0755, true);
}

if (!is_dir($pastaUploads) || !is_writable($pastaUploads)) {
    responderJson(
        false,
        'A pasta uploads não existe ou não permite gravação.',
        500
    );
}

try {
    $codigo = bin2hex(random_bytes(16));
} catch (Throwable $erro) {
    $codigo = sha1(uniqid((string) mt_rand(), true));
}

$nomeArquivo =
    date('Ymd_His') .
    '_' .
    $codigo .
    '.' .
    $formatos[$mime];

$caminhoFisico =
    $pastaUploads .
    DIRECTORY_SEPARATOR .
    $nomeArquivo;

$caminhoBanco = 'pele/uploads/' . $nomeArquivo;

if (!move_uploaded_file($temporario, $caminhoFisico)) {
    responderJson(
        false,
        'Não foi possível salvar a foto na pasta uploads.',
        500
    );
}

@chmod($caminhoFisico, 0644);

// -------------------------------------------------------------------------
// EXECUTA O ANALISADOR LOCAL
// -------------------------------------------------------------------------

/*
 * NÃO use "2>&1" aqui.
 *
 * Bibliotecas como MediaPipe, TensorFlow Lite e OpenCV podem escrever
 * avisos no STDERR. Quando STDERR é misturado ao STDOUT, esses avisos
 * ficam junto do JSON e fazem json_decode() falhar.
 */
$descritores = [
    0 => ['pipe', 'r'],
    1 => ['pipe', 'w'],
    2 => ['pipe', 'w']
];

$comando = [
    $pythonExecutavel,
    $scriptAnalisador,
    $caminhoFisico
];

$processo = @proc_open(
    $comando,
    $descritores,
    $pipes,
    null,
    [
        'TF_CPP_MIN_LOG_LEVEL' => '3',
        'GLOG_minloglevel' => '3',
        'MEDIAPIPE_DISABLE_GPU' => '1',
        'PYTHONUNBUFFERED' => '1',
        'PYTHONIOENCODING' => 'utf-8'
    ]
);

if (!is_resource($processo)) {
    apagarArquivoSeguro($caminhoFisico);

    responderJson(
        false,
        'Não foi possível iniciar o analisador Python.',
        500
    );
}

fclose($pipes[0]);

$saidaStdout = stream_get_contents($pipes[1]);
$saidaStderr = stream_get_contents($pipes[2]);

fclose($pipes[1]);
fclose($pipes[2]);

$codigoSaida = proc_close($processo);

$saidaStdout = trim((string) $saidaStdout);
$saidaStderr = trim((string) $saidaStderr);

/*
 * Primeiro tenta decodificar o STDOUT completo.
 */
$dados = json_decode($saidaStdout, true);

/*
 * Caso alguma biblioteca tenha escrito texto no STDOUT, procura de baixo
 * para cima a última linha que seja um JSON completo.
 */
if (!is_array($dados) && $saidaStdout !== '') {
    $linhasJson = preg_split(
        '/\R/u',
        $saidaStdout
    );

    if (is_array($linhasJson)) {
        for (
            $indice = count($linhasJson) - 1;
            $indice >= 0;
            $indice--
        ) {
            $linha = trim(
                (string) $linhasJson[$indice]
            );

            if ($linha === '') {
                continue;
            }

            $possivelJson = json_decode(
                $linha,
                true
            );

            if (is_array($possivelJson)) {
                $dados = $possivelJson;
                break;
            }
        }
    }
}

/*
 * Última tentativa: extrai o conteúdo entre a primeira chave de abertura
 * e a última chave de fechamento.
 */
if (!is_array($dados) && $saidaStdout !== '') {
    $inicioJson = strpos(
        $saidaStdout,
        '{'
    );

    $fimJson = strrpos(
        $saidaStdout,
        '}'
    );

    if (
        $inicioJson !== false &&
        $fimJson !== false &&
        $fimJson > $inicioJson
    ) {
        $trechoJson = substr(
            $saidaStdout,
            $inicioJson,
            $fimJson - $inicioJson + 1
        );

        $possivelJson = json_decode(
            $trechoJson,
            true
        );

        if (is_array($possivelJson)) {
            $dados = $possivelJson;
        }
    }
}

if (!is_array($dados)) {
    apagarArquivoSeguro($caminhoFisico);

    error_log(
        '[PELE PYTHON] Saída inválida. Código: ' .
        $codigoSaida .
        ' | STDOUT: ' .
        substr($saidaStdout, 0, 3000) .
        ' | STDERR: ' .
        substr($saidaStderr, 0, 3000)
    );

    responderJson(
        false,
        'O analisador local não conseguiu gerar um resultado válido.',
        500,
        [
            'codigo_python' => $codigoSaida,
            'stdout' => substr(
                $saidaStdout,
                0,
                1200
            ),
            'stderr' => substr(
                $saidaStderr,
                0,
                1200
            ),
            'orientacao' =>
                'Execute o analisador pelo terminal para visualizar o erro real.'
        ]
    );
}

if (
    $codigoSaida !== 0 ||
    !isset($dados['sucesso']) ||
    $dados['sucesso'] !== true
) {
    $mensagemErro =
        isset($dados['mensagem']) && is_string($dados['mensagem'])
            ? $dados['mensagem']
            : 'Não foi possível analisar a fotografia.';

    apagarArquivoSeguro($caminhoFisico);

    responderJson(
        false,
        $mensagemErro,
        422
    );
}

// -------------------------------------------------------------------------
// EXTRAI CAMPOS
// -------------------------------------------------------------------------

$resultadoCompleto = valorTexto(
    $dados,
    ['resultado_completo'],
    'Análise concluída.'
);

$qualidadeImagem = valorTexto(
    $dados,
    ['qualidade_imagem'],
    'REGULAR'
);

$observacoesQualidade = valorTexto(
    $dados,
    ['observacoes_qualidade']
);

$perfilCodigo = valorTexto(
    $dados,
    ['perfil_codigo'],
    'PELE_EQUILIBRADA'
);

$perfilMotivo = valorTexto(
    $dados,
    ['perfil_motivo']
);

$statusVisual = valorTexto(
    $dados,
    ['status_visual'],
    'APARÊNCIA BOA'
);

$oleosidadeNivel = valorTexto(
    $dados,
    ['oleosidade', 'nivel']
);

$oleosidadeValor = valorNumero(
    $dados,
    ['oleosidade', 'valor']
);

$oleosidadeDescricao = valorTexto(
    $dados,
    ['oleosidade', 'descricao']
);

$porosNivel = valorTexto(
    $dados,
    ['poros', 'nivel']
);

$porosValor = valorNumero(
    $dados,
    ['poros', 'valor']
);

$porosDescricao = valorTexto(
    $dados,
    ['poros', 'descricao']
);

$linhasNivel = valorTexto(
    $dados,
    ['linhas_expressao', 'nivel']
);

$linhasValor = valorNumero(
    $dados,
    ['linhas_expressao', 'valor']
);

$linhasDescricao = valorTexto(
    $dados,
    ['linhas_expressao', 'descricao']
);

$tonalidadeNivel = valorTexto(
    $dados,
    ['tonalidade', 'nivel']
);

$tonalidadeValor = valorNumero(
    $dados,
    ['tonalidade', 'valor']
);

$tonalidadeDescricao = valorTexto(
    $dados,
    ['tonalidade', 'descricao']
);

$hidratacaoValor = valorNumero(
    $dados,
    ['hidratacao', 'valor']
);

$hidratacaoDescricao = valorTexto(
    $dados,
    ['hidratacao', 'descricao']
);

$vermelhidaoNivel = valorTexto(
    $dados,
    ['vermelhidao', 'nivel']
);

$vermelhidaoValor = valorNumero(
    $dados,
    ['vermelhidao', 'valor']
);

$vermelhidaoDescricao = valorTexto(
    $dados,
    ['vermelhidao', 'descricao']
);

$recomendacoes = valorTexto(
    $dados,
    ['recomendacoes']
);

$avisoImportante = valorTexto(
    $dados,
    ['aviso_importante']
);

$rostoDetectado =
    !empty($dados['rosto_detectado'])
        ? 1
        : 0;

$coberturaPele = valorNumero(
    $dados,
    ['cobertura_pele']
);

$dadosJson = json_encode(
    $dados,
    JSON_UNESCAPED_UNICODE |
    JSON_UNESCAPED_SLASHES |
    JSON_INVALID_UTF8_SUBSTITUTE
);

// -------------------------------------------------------------------------
// CONEXÃO E GRAVAÇÃO NO MYSQL
// -------------------------------------------------------------------------

mysqli_report(MYSQLI_REPORT_OFF);

$conexao = @new mysqli($host, $user, $pass, $db);

if ($conexao->connect_errno) {
    apagarArquivoSeguro($caminhoFisico);

    responderJson(
        false,
        'Erro de conexão com o MySQL: ' .
        $conexao->connect_error,
        500
    );
}

$conexao->set_charset('utf8mb4');

$sqlCriar = "
    CREATE TABLE IF NOT EXISTS analises_pele_local (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        caminho_foto VARCHAR(500) NOT NULL,
        qualidade_imagem VARCHAR(30) NOT NULL,
        observacoes_qualidade TEXT NULL,
        rosto_detectado TINYINT(1) NOT NULL DEFAULT 0,
        cobertura_pele DECIMAL(8,5) NULL,
        perfil_codigo VARCHAR(60) NOT NULL,
        perfil_motivo TEXT NULL,
        status_visual VARCHAR(100) NOT NULL,
        oleosidade_nivel VARCHAR(30) NULL,
        oleosidade_valor DECIMAL(6,2) NULL,
        oleosidade_descricao TEXT NULL,
        poros_nivel VARCHAR(30) NULL,
        poros_valor DECIMAL(6,2) NULL,
        poros_descricao TEXT NULL,
        linhas_nivel VARCHAR(30) NULL,
        linhas_valor DECIMAL(6,2) NULL,
        linhas_descricao TEXT NULL,
        tonalidade_nivel VARCHAR(30) NULL,
        tonalidade_valor DECIMAL(6,2) NULL,
        tonalidade_descricao TEXT NULL,
        hidratacao_valor DECIMAL(6,2) NULL,
        hidratacao_descricao TEXT NULL,
        vermelhidao_nivel VARCHAR(30) NULL,
        vermelhidao_valor DECIMAL(6,2) NULL,
        vermelhidao_descricao TEXT NULL,
        recomendacoes LONGTEXT NULL,
        aviso_importante TEXT NULL,
        resultado_completo LONGTEXT NOT NULL,
        dados_json LONGTEXT NULL,
        motor VARCHAR(50) NOT NULL DEFAULT 'OpenCV local',
        criado_em DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY idx_analises_local_data (criado_em),
        KEY idx_analises_local_perfil (perfil_codigo)
    ) ENGINE=InnoDB
      DEFAULT CHARSET=utf8mb4
      COLLATE=utf8mb4_unicode_ci
";

if (!$conexao->query($sqlCriar)) {
    $erroTabela = $conexao->error;
    $conexao->close();
    apagarArquivoSeguro($caminhoFisico);

    responderJson(
        false,
        'Erro ao criar/verificar a tabela: ' . $erroTabela,
        500
    );
}

$sqlInsert = "
    INSERT INTO analises_pele_local (
        caminho_foto,
        qualidade_imagem,
        observacoes_qualidade,
        rosto_detectado,
        cobertura_pele,
        perfil_codigo,
        perfil_motivo,
        status_visual,
        oleosidade_nivel,
        oleosidade_valor,
        oleosidade_descricao,
        poros_nivel,
        poros_valor,
        poros_descricao,
        linhas_nivel,
        linhas_valor,
        linhas_descricao,
        tonalidade_nivel,
        tonalidade_valor,
        tonalidade_descricao,
        hidratacao_valor,
        hidratacao_descricao,
        vermelhidao_nivel,
        vermelhidao_valor,
        vermelhidao_descricao,
        recomendacoes,
        aviso_importante,
        resultado_completo,
        dados_json
    ) VALUES (
        ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
        ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
        ?, ?, ?, ?, ?, ?, ?
    )
";

$stmt = $conexao->prepare($sqlInsert);

if ($stmt === false) {
    $erroPrepare = $conexao->error;
    $conexao->close();
    apagarArquivoSeguro($caminhoFisico);

    responderJson(
        false,
        'Erro ao preparar a gravação: ' . $erroPrepare,
        500
    );
}

$stmt->bind_param(
    'sssidssssdssdssdssdsdssdsssss',
    $caminhoBanco,
    $qualidadeImagem,
    $observacoesQualidade,
    $rostoDetectado,
    $coberturaPele,
    $perfilCodigo,
    $perfilMotivo,
    $statusVisual,
    $oleosidadeNivel,
    $oleosidadeValor,
    $oleosidadeDescricao,
    $porosNivel,
    $porosValor,
    $porosDescricao,
    $linhasNivel,
    $linhasValor,
    $linhasDescricao,
    $tonalidadeNivel,
    $tonalidadeValor,
    $tonalidadeDescricao,
    $hidratacaoValor,
    $hidratacaoDescricao,
    $vermelhidaoNivel,
    $vermelhidaoValor,
    $vermelhidaoDescricao,
    $recomendacoes,
    $avisoImportante,
    $resultadoCompleto,
    $dadosJson
);

if (!$stmt->execute()) {
    $erroInsert = $stmt->error;

    $stmt->close();
    $conexao->close();
    apagarArquivoSeguro($caminhoFisico);

    responderJson(
        false,
        'A análise foi realizada, mas não foi salva no banco: ' .
        $erroInsert,
        500
    );
}

$idAnalise = (int) $conexao->insert_id;

$stmt->close();

$sqlVinculo = "
    INSERT INTO paciente_analises (
        paciente_id,
        analise_id
    ) VALUES (?, ?)
";

$stmtVinculo = $conexao->prepare($sqlVinculo);

if (!$stmtVinculo) {
    $conexao->query(
        'DELETE FROM analises_pele_local WHERE id = ' .
        (int) $idAnalise
    );

    $conexao->close();
    apagarArquivoSeguro($caminhoFisico);

    responderJson(
        false,
        'A análise foi feita, mas não foi possível vinculá-la ao paciente.',
        500
    );
}

$stmtVinculo->bind_param(
    'ii',
    $pacienteId,
    $idAnalise
);

if (!$stmtVinculo->execute()) {
    $erroVinculo = $stmtVinculo->error;

    $stmtVinculo->close();

    $conexao->query(
        'DELETE FROM analises_pele_local WHERE id = ' .
        (int) $idAnalise
    );

    $conexao->close();
    apagarArquivoSeguro($caminhoFisico);

    responderJson(
        false,
        'Erro ao vincular a análise ao paciente: ' .
        $erroVinculo,
        500
    );
}

$stmtVinculo->close();
$conexao->close();

// -------------------------------------------------------------------------
// RESPOSTA COMPATÍVEL COM A ACTIVITY ATUAL
// -------------------------------------------------------------------------

responderJson(
    true,
    $resultadoCompleto,
    200,
    [
        'resultado' => $resultadoCompleto,
        'id_analise' => $idAnalise,
        'paciente_id' => $pacienteId,
        'caminho_foto' => $caminhoBanco,
        'perfil_codigo' => $perfilCodigo,
        'analise' => $dados,
        'largura_imagem' => $larguraImagem,
        'altura_imagem' => $alturaImagem,
        'provedor' => 'ANALISADOR_LOCAL_OPENCV'
    ]
);
