<?php
/**
 * 1MZ For Artists — Uploads Controller
 *
 * Endpoints para receber ficheiros (capa de release + áudio de tracks).
 *
 * Reusa as funções existentes do plugin:
 *   - rmbz_handle_proof_upload (capa)
 *   - rmbz_validate_audio_file (áudio)
 *   - wp_handle_upload (genérico)
 *
 * Padrão de áudio: o cliente faz upload, recebe um upload_token opaco
 * (40 chars). O save da track recebe esse token, valida via transient,
 * e consome (one-shot). Mesmo padrão que o site web usa internamente.
 *
 * @package 1MZ_For_Artists
 * @since 1.6.4
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * [1.6.50.4] Detecta o formato de imagem pelos magic bytes (primeiros 16 bytes
 * do ficheiro). Não depende de extensões PHP externas (finfo, exif, gd).
 *
 * Retorna: 'jpeg' | 'png' | 'webp' | 'heic' | 'gif' | 'bmp' | 'tiff'
 *          | 'unknown' (formato não reconhecido)
 *          | false (não conseguiu ler o ficheiro)
 *
 * Esta é a forma mais segura de validar o formato real — os bytes do
 * ficheiro não mentem, ao contrário de extensão, MIME do cliente, ou
 * MIME do ContentResolver.
 */
function rmbz_api_detect_image_format_by_magic_bytes( $file_path ) {
    $fp = @fopen( $file_path, 'rb' );
    if ( ! $fp ) return false;
    $header = fread( $fp, 16 );
    fclose( $fp );
    if ( $header === false || strlen( $header ) < 4 ) return 'unknown';

    $b = array_values( unpack( 'C*', $header ) );

    // JPEG: FF D8 FF
    if ( $b[0] === 0xFF && $b[1] === 0xD8 && $b[2] === 0xFF ) {
        return 'jpeg';
    }
    // PNG: 89 50 4E 47 0D 0A 1A 0A
    if ( count( $b ) >= 8 &&
         $b[0] === 0x89 && $b[1] === 0x50 && $b[2] === 0x4E && $b[3] === 0x47 &&
         $b[4] === 0x0D && $b[5] === 0x0A && $b[6] === 0x1A && $b[7] === 0x0A ) {
        return 'png';
    }
    // WebP: "RIFF" + 4 bytes + "WEBP"
    if ( count( $b ) >= 12 &&
         $b[0] === 0x52 && $b[1] === 0x49 && $b[2] === 0x46 && $b[3] === 0x46 &&
         $b[8] === 0x57 && $b[9] === 0x45 && $b[10] === 0x42 && $b[11] === 0x50 ) {
        return 'webp';
    }
    // HEIC/HEIF: bytes 4-7 = "ftyp", depois 8-11 ∈ {heic, heix, hevc, mif1, ...}
    if ( count( $b ) >= 12 &&
         $b[4] === 0x66 && $b[5] === 0x74 && $b[6] === 0x79 && $b[7] === 0x70 ) {
        $brand = substr( $header, 8, 4 );
        if ( in_array( $brand, [ 'heic', 'heix', 'hevc', 'heim', 'heis', 'hevm', 'hevs', 'mif1', 'msf1', 'heif' ], true ) ) {
            return 'heic';
        }
    }
    // GIF: "GIF8"
    if ( $b[0] === 0x47 && $b[1] === 0x49 && $b[2] === 0x46 && $b[3] === 0x38 ) {
        return 'gif';
    }
    // BMP: "BM"
    if ( $b[0] === 0x42 && $b[1] === 0x4D ) {
        return 'bmp';
    }
    // TIFF: "II*\0" ou "MM\0*"
    if ( count( $b ) >= 4 &&
         ( ( $b[0] === 0x49 && $b[1] === 0x49 && $b[2] === 0x2A && $b[3] === 0x00 ) ||
           ( $b[0] === 0x4D && $b[1] === 0x4D && $b[2] === 0x00 && $b[3] === 0x2A ) ) ) {
        return 'tiff';
    }
    return 'unknown';
}

add_action( 'rest_api_init', 'rmbz_api_register_uploads_routes' );

function rmbz_api_register_uploads_routes() {

    register_rest_route( RMBZ_API_NAMESPACE, '/uploads/cover', [
        'methods'             => 'POST',
        'callback'            => 'rmbz_api_handle_upload_cover',
        'permission_callback' => 'rmbz_api_require_auth',
    ] );

    register_rest_route( RMBZ_API_NAMESPACE, '/uploads/audio', [
        'methods'             => 'POST',
        'callback'            => 'rmbz_api_handle_upload_audio',
        'permission_callback' => 'rmbz_api_require_auth',
    ] );
}

/* ============================================================================
 * POST /uploads/cover
 *
 * Multipart com campo "cover" — JPG ou PNG, mínimo 1400x1400, máximo 10MB.
 * Devolve a URL absoluta do ficheiro guardado.
 * ============================================================================ */

function rmbz_api_handle_upload_cover( WP_REST_Request $request ) {
    $telemovel = (string) $request->get_param( 'rmbz_telemovel' );

    // Rate limit (10 capas/min — generoso, capa é leve)
    if ( function_exists( 'rmbz_rate_limit' )
        && ! rmbz_rate_limit( 'api_upload_cover_' . $telemovel, 10 ) ) {
        return rmbz_api_error( 'Demasiados uploads. Aguarda um minuto.', 429, 'rate_limit' );
    }

    // O ficheiro vem em $_FILES['cover']
    $files = $request->get_file_params();
    if ( empty( $files['cover'] ) || ! isset( $files['cover']['tmp_name'] ) ) {
        return rmbz_api_error( 'Nenhum ficheiro recebido (campo "cover").', 400, 'no_file' );
    }
    $file = $files['cover'];

    // Erros do PHP (tamanho excedido, etc)
    if ( $file['error'] !== UPLOAD_ERR_OK ) {
        $msg = match ( $file['error'] ) {
            UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'Ficheiro demasiado grande (limite do servidor).',
            UPLOAD_ERR_PARTIAL => 'Upload interrompido.',
            UPLOAD_ERR_NO_TMP_DIR => 'Pasta temporária em falta no servidor.',
            UPLOAD_ERR_CANT_WRITE => 'Falha ao escrever no disco.',
            default => 'Erro no upload (código ' . $file['error'] . ').',
        };
        return rmbz_api_error( $msg, 400, 'upload_error' );
    }

    // Tamanho
    $size = (int) ( $file['size'] ?? 0 );
    if ( $size > 10 * 1024 * 1024 ) {
        return rmbz_api_error( 'Capa demasiado grande (máx. 10 MB).', 400, 'too_large' );
    }
    if ( $size <= 0 ) {
        return rmbz_api_error( 'Ficheiro vazio.', 400, 'empty_file' );
    }

    // [1.6.50.4] Validação MIME REAL — lê os primeiros bytes (magic bytes)
    // independente de extensões PHP externas (finfo pode não estar instalada).
    $tmp = $file['tmp_name'];
    $detected_format = rmbz_api_detect_image_format_by_magic_bytes( $tmp );
    if ( $detected_format === false ) {
        return rmbz_api_error( 'Não foi possível ler o ficheiro.', 400, 'read_failed' );
    }
    if ( ! in_array( $detected_format, [ 'jpeg', 'png' ], true ) ) {
        $friendly = strtoupper( $detected_format );
        return rmbz_api_error(
            sprintf(
                'Formato %s não é aceite. Usa apenas JPG ou PNG (Spotify e Apple Music só aceitam estes).',
                $friendly
            ),
            400,
            'invalid_mime'
        );
    }

    // Defesa em profundidade — também correr o verify_upload_mime se finfo
    // estiver disponível. Se finfo não existir, fica só na detecção acima.
    if ( function_exists( 'rmbz_verify_upload_mime' ) && function_exists( 'finfo_open' ) ) {
        $mime_check = rmbz_verify_upload_mime( $tmp, [ 'image/jpeg', 'image/png' ] );
        if ( $mime_check !== true ) {
            return rmbz_api_error( $mime_check, 400, 'invalid_mime' );
        }
    }

    // Dimensões mínimas
    $size_info = @getimagesize( $tmp );
    if ( ! $size_info ) {
        return rmbz_api_error( 'Não foi possível ler as dimensões da imagem.', 400, 'invalid_image' );
    }
    if ( $size_info[0] < 1400 || $size_info[1] < 1400 ) {
        return rmbz_api_error(
            sprintf( 'Capa precisa de ter pelo menos 1400×1400 pixels (a tua: %d×%d). Spotify e Apple Music exigem este mínimo.', $size_info[0], $size_info[1] ),
            422,
            'too_small'
        );
    }

    // [1.6.50.5] Proporção quadrada (1:1) obrigatória.
    // Spotify, Apple Music, Tidal etc. exigem capas quadradas. Não-quadrado
    // resulta em cortes automáticos que estragam o design. Aceita 1% de
    // tolerância para acomodar arredondamento de exportadores.
    $w = $size_info[0];
    $h = $size_info[1];
    $ratio_diff = abs( $w - $h ) / max( $w, $h );
    if ( $ratio_diff > 0.01 ) {
        return rmbz_api_error(
            sprintf(
                'Capa tem de ser quadrada (1:1). A tua é %d×%d. Spotify e Apple Music exigem proporção 1:1 — corta a imagem em quadrado antes de fazer upload.',
                $w,
                $h
            ),
            422,
            'not_square'
        );
    }

    // Mover para uploads/ usando wp_handle_upload (gera nome único, usa estrutura de meses)
    if ( ! function_exists( 'wp_handle_upload' ) ) {
        require_once ABSPATH . 'wp-admin/includes/file.php';
    }

    $overrides = [
        'test_form' => false,
        'mimes'     => [ 'jpg|jpeg' => 'image/jpeg', 'png' => 'image/png' ],
    ];

    // Fingir que o ficheiro está em $_FILES porque wp_handle_upload precisa
    $_FILES['cover'] = $file;
    $movefile = wp_handle_upload( $file, $overrides );

    if ( isset( $movefile['error'] ) ) {
        error_log( '[1MZ-API] cover upload error: ' . $movefile['error'] );
        return rmbz_api_error( 'Falha a guardar a capa: ' . $movefile['error'], 500, 'upload_failed' );
    }

    return rmbz_api_ok( [
        'url'    => $movefile['url'],
        'width'  => $size_info[0],
        'height' => $size_info[1],
        'size'   => $size,
    ] );
}

/* ============================================================================
 * POST /uploads/audio
 *
 * Multipart com campo "audio" — WAV/FLAC/MP3, validado por
 * rmbz_validate_audio_file. Devolve um upload_token (opaco, 40 chars),
 * que o cliente passa ao endpoint de criar track.
 *
 * O token expira em 30 min e é one-shot (consumido na criação da track).
 * ============================================================================ */

function rmbz_api_handle_upload_audio( WP_REST_Request $request ) {
    $telemovel = (string) $request->get_param( 'rmbz_telemovel' );

    // Rate limit (5 uploads/min — ficheiros grandes)
    if ( function_exists( 'rmbz_rate_limit' )
        && ! rmbz_rate_limit( 'api_upload_audio_' . $telemovel, 5 ) ) {
        return rmbz_api_error( 'Demasiados uploads. Aguarda um minuto.', 429, 'rate_limit' );
    }

    $files = $request->get_file_params();
    if ( empty( $files['audio'] ) || ! isset( $files['audio']['tmp_name'] ) ) {
        return rmbz_api_error( 'Nenhum ficheiro recebido (campo "audio").', 400, 'no_file' );
    }
    $file = $files['audio'];

    if ( $file['error'] !== UPLOAD_ERR_OK ) {
        $msg = match ( $file['error'] ) {
            UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'Ficheiro demasiado grande (limite do servidor: ' . ini_get( 'upload_max_filesize' ) . ').',
            UPLOAD_ERR_PARTIAL => 'Upload interrompido. Tenta novamente.',
            UPLOAD_ERR_NO_TMP_DIR => 'Pasta temporária em falta no servidor.',
            UPLOAD_ERR_CANT_WRITE => 'Falha ao escrever no disco.',
            default => 'Erro no upload (código ' . $file['error'] . ').',
        };
        return rmbz_api_error( $msg, 400, 'upload_error' );
    }

    $size = (int) ( $file['size'] ?? 0 );
    if ( $size > 200 * 1024 * 1024 ) {
        return rmbz_api_error( 'Áudio demasiado grande (máx. 200 MB).', 400, 'too_large' );
    }
    if ( $size <= 0 ) {
        return rmbz_api_error( 'Ficheiro vazio.', 400, 'empty_file' );
    }

    $tmp = $file['tmp_name'];

    // Validar MIME — aceitar audio/*
    if ( function_exists( 'rmbz_verify_upload_mime' ) ) {
        $accepted_mimes = [
            'audio/wav',
            'audio/x-wav',
            'audio/wave',
            'audio/mpeg',
            'audio/mp3',
            'audio/flac',
            'audio/x-flac',
        ];
        $mime_check = rmbz_verify_upload_mime( $tmp, $accepted_mimes );
        if ( $mime_check !== true ) {
            return rmbz_api_error( $mime_check, 400, 'invalid_mime' );
        }
    }

    // Validar áudio (qualidade) usando o validador do plugin
    $validation_info = [];
    $validation_warnings = [];
    if ( function_exists( 'rmbz_validate_audio_file' ) ) {
        $validation = rmbz_validate_audio_file( $tmp );
        if ( ! $validation['ok'] ) {
            $errors_text = ! empty( $validation['errors'] )
                ? implode( ' ', $validation['errors'] )
                : 'Áudio não passou na validação.';
            return rmbz_api_error( $errors_text, 422, 'audio_invalid' );
        }
        $validation_info     = $validation['info'] ?? [];
        $validation_warnings = $validation['warnings'] ?? [];
    }

    // Mover para uploads/
    if ( ! function_exists( 'wp_handle_upload' ) ) {
        require_once ABSPATH . 'wp-admin/includes/file.php';
    }

    $overrides = [
        'test_form' => false,
        'mimes'     => [
            'wav|wave' => 'audio/wav',
            'mp3'      => 'audio/mpeg',
            'flac'     => 'audio/flac',
        ],
    ];
    $_FILES['audio'] = $file;
    $movefile = wp_handle_upload( $file, $overrides );

    if ( isset( $movefile['error'] ) ) {
        error_log( '[1MZ-API] audio upload error: ' . $movefile['error'] );
        return rmbz_api_error( 'Falha a guardar o áudio: ' . $movefile['error'], 500, 'upload_failed' );
    }

    // Gerar token opaco e guardar em transient (30 min)
    $upload_token = wp_generate_password( 40, false, false );
    set_transient( 'rmbz_upl_' . $upload_token, [
        'url'        => $movefile['url'],
        'telemovel'  => $telemovel,
        'size'       => $size,
        'audio_info' => $validation_info,
        'issued_at'  => time(),
    ], 30 * MINUTE_IN_SECONDS );

    $response = [
        'upload_token' => $upload_token,
        'audio_info'   => $validation_info,
        'expires_in'   => 30 * 60, // segundos
    ];
    if ( ! empty( $validation_warnings ) ) {
        $response['warnings'] = $validation_warnings;
    }

    return rmbz_api_ok( $response );
}
