<?php
namespace Domain\Update;

class UpdateRepository
{
    private $db;

    public function __construct( $db )
    {
        $this->db = $db;
    }

    /**
     * Wykonuje aktualizację do następnej wersji.
     *
     * @return array{success: bool, log: array, no_updates?: bool}
     */
    public function update(): array
    {
        global $settings;

        @file_put_contents( '../libraries/update_log.txt', '' );

        $log = [];
        $log[] = '[START] Rozpoczęcie aktualizacji - ' . date( 'Y-m-d H:i:s' );
        $log[] = '[INFO] Aktualna wersja: ' . \Shared\Helpers\Helpers::get_version();

        \Shared\Helpers\Helpers::delete_session( 'new-version' );

        $versionsUrl = 'https://shoppro.project-dc.pl/updates/versions.php?key=' . $settings['update_key'];
        $versions = @file_get_contents( $versionsUrl );

        if ( $versions === false ) {
            $log[] = '[ERROR] Nie udało się pobrać listy wersji z: ' . $versionsUrl;
            $this->saveLog( $log );
            return [ 'success' => false, 'log' => $log ];
        }

        $log[] = '[OK] Pobrano listę wersji';
        $versions = explode( PHP_EOL, $versions );
        $log[] = '[INFO] Znaleziono ' . count( $versions ) . ' wersji do sprawdzenia';

        foreach ( $versions as $ver ) {
            $ver = trim( $ver );
            if ( floatval( $ver ) <= (float) \Shared\Helpers\Helpers::get_version() ) {
                continue;
            }

            $log[] = '[INFO] Aktualizacja do wersji: ' . $ver;
            $dir = strlen( $ver ) == 5
                ? substr( $ver, 0, strlen( $ver ) - 2 ) . '0'
                : substr( $ver, 0, strlen( $ver ) - 1 ) . '0';

            $result = $this->downloadAndApply( $ver, $dir, $log );
            $this->saveLog( $result['log'] );
            return $result;
        }

        $log[] = '[INFO] Brak nowych wersji do zainstalowania';
        $this->saveLog( $log );
        return [ 'success' => true, 'log' => $log, 'no_updates' => true ];
    }

    private function downloadAndApply( string $ver, string $dir, array $log ): array
    {
        $baseUrl = 'https://shoppro.project-dc.pl/updates/' . $dir;

        // Pobieranie ZIP
        $zipUrl = $baseUrl . '/ver_' . $ver . '.zip';
        $log[] = '[INFO] Pobieranie pliku ZIP: ' . $zipUrl;
        $file = @file_get_contents( $zipUrl );

        if ( $file === false ) {
            $log[] = '[ERROR] Nie udało się pobrać pliku ZIP';
            return [ 'success' => false, 'log' => $log ];
        }

        $fileSize = strlen( $file );
        $log[] = '[OK] Pobrano plik ZIP, rozmiar: ' . $fileSize . ' bajtów';

        if ( $fileSize < 100 ) {
            $log[] = '[ERROR] Plik ZIP jest za mały (prawdopodobnie błąd pobierania)';
            return [ 'success' => false, 'log' => $log ];
        }

        $dlHandler = @fopen( 'update.zip', 'w' );
        if ( !$dlHandler ) {
            $log[] = '[ERROR] Nie udało się otworzyć pliku update.zip do zapisu';
            $log[] = '[INFO] Katalog roboczy: ' . getcwd();
            return [ 'success' => false, 'log' => $log ];
        }

        $written = fwrite( $dlHandler, $file );
        fclose( $dlHandler );

        if ( $written === false || $written === 0 ) {
            $log[] = '[ERROR] Nie udało się zapisać pliku ZIP';
            return [ 'success' => false, 'log' => $log ];
        }

        $log[] = '[OK] Zapisano plik ZIP (' . $written . ' bajtów)';

        // Wykonanie SQL
        $log = $this->executeSql( $baseUrl . '/ver_' . $ver . '_sql.txt', $log );

        // Usuwanie plików
        $log = $this->deleteFiles( $baseUrl . '/ver_' . $ver . '_files.txt', $log );

        // Rozpakowywanie ZIP
        $log = $this->extractZip( 'update.zip', $log );

        // Aktualizacja wersji
        $versionFile = '../libraries/version.ini';
        $handle = @fopen( $versionFile, 'w' );
        if ( !$handle ) {
            $log[] = '[ERROR] Nie udało się otworzyć pliku version.ini do zapisu';
            return [ 'success' => false, 'log' => $log ];
        }
        fwrite( $handle, $ver );
        fclose( $handle );

        $log[] = '[OK] Zaktualizowano plik version.ini do wersji: ' . $ver;
        $log[] = '[SUCCESS] Aktualizacja do wersji ' . $ver . ' zakończona pomyślnie';

        return [ 'success' => true, 'log' => $log ];
    }

    private function executeSql( string $sqlUrl, array $log ): array
    {
        $log[] = '[INFO] Sprawdzanie aktualizacji SQL: ' . $sqlUrl;

        $ch = curl_init( $sqlUrl );
        curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
        curl_setopt( $ch, CURLOPT_HEADER, false );
        $response = curl_exec( $ch );
        $contentType = curl_getinfo( $ch, CURLINFO_CONTENT_TYPE );
        $httpCode = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
        curl_close( $ch );

        if ( !$response || strpos( $contentType, 'text/plain' ) === false ) {
            $log[] = '[INFO] Brak aktualizacji SQL (HTTP: ' . $httpCode . ')';
            return $log;
        }

        $queries = explode( PHP_EOL, $response );
        $log[] = '[OK] Pobrano ' . count( $queries ) . ' zapytań SQL';
        $success = 0;
        $errors = 0;

        foreach ( $queries as $query ) {
            $query = trim( $query );
            if ( $query !== '' ) {
                if ( $this->db->query( $query ) ) {
                    $success++;
                } else {
                    $errors++;
                }
            }
        }

        $log[] = '[INFO] Wykonano zapytania SQL - sukces: ' . $success . ', błędy: ' . $errors;
        return $log;
    }

    private function deleteFiles( string $filesUrl, array $log ): array
    {
        $log[] = '[INFO] Sprawdzanie plików do usunięcia: ' . $filesUrl;

        $ch = curl_init( $filesUrl );
        curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
        curl_setopt( $ch, CURLOPT_HEADER, false );
        $response = curl_exec( $ch );
        $contentType = curl_getinfo( $ch, CURLINFO_CONTENT_TYPE );
        curl_close( $ch );

        if ( !$response || strpos( $contentType, 'text/plain' ) === false ) {
            $log[] = '[INFO] Brak plików do usunięcia';
            return $log;
        }

        $files = explode( PHP_EOL, $response );
        $deletedFiles = 0;
        $deletedDirs = 0;

        foreach ( $files as $entry ) {
            if ( strpos( $entry, 'F: ' ) !== false ) {
                $path = substr( $entry, 3 );
                if ( file_exists( $path ) ) {
                    if ( @unlink( $path ) ) {
                        $deletedFiles++;
                    } else {
                        $log[] = '[WARNING] Nie udało się usunąć pliku: ' . $path;
                    }
                }
            }

            if ( strpos( $entry, 'D: ' ) !== false ) {
                $path = substr( $entry, 3 );
                if ( is_dir( $path ) ) {
                    \Shared\Helpers\Helpers::delete_dir( $path );
                    $deletedDirs++;
                }
            }
        }

        $log[] = '[INFO] Usunięto plików: ' . $deletedFiles . ', katalogów: ' . $deletedDirs;
        return $log;
    }

    private function extractZip( string $fileName, array $log ): array
    {
        $log[] = '[INFO] Rozpoczęcie rozpakowywania pliku ZIP';

        $path = pathinfo( realpath( $fileName ), PATHINFO_DIRNAME );
        $path = substr( $path, 0, strlen( $path ) - 5 );

        if ( !is_dir( $path ) || !is_writable( $path ) ) {
            $log[] = '[ERROR] Ścieżka docelowa nie istnieje lub brak uprawnień: ' . $path;
            return $log;
        }

        $zip = new \ZipArchive;
        $res = $zip->open( $fileName );

        if ( $res !== true ) {
            $log[] = '[ERROR] Nie udało się otworzyć pliku ZIP (kod: ' . $res . ')';
            return $log;
        }

        $log[] = '[OK] Otwarto archiwum ZIP, liczba plików: ' . $zip->numFiles;
        $extracted = 0;
        $errors = 0;

        for ( $i = 0; $i < $zip->numFiles; $i++ ) {
            $filename = str_replace( '\\', '/', $zip->getNameIndex( $i ) );

            if ( substr( $filename, -1 ) === '/' ) {
                $dirPath = $path . '/' . $filename;
                if ( !is_dir( $dirPath ) ) {
                    @mkdir( $dirPath, 0755, true );
                }
                continue;
            }

            $targetFile = $path . '/' . $filename;
            $targetDir = dirname( $targetFile );

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

            $existed = file_exists( $targetFile );
            $content = $zip->getFromIndex( $i );

            if ( $content === false ) {
                $log[] = '[ERROR] Nie udało się odczytać z ZIP: ' . $filename;
                $errors++;
                continue;
            }

            if ( @file_put_contents( $targetFile, $content ) === false ) {
                $log[] = '[ERROR] Nie udało się zapisać: ' . $filename;
                $errors++;
            } else {
                $tag = $existed ? '[UPDATED]' : '[NEW]';
                $log[] = $tag . ' ' . $filename . ' (' . strlen( $content ) . ' bajtów)';
                $extracted++;
            }
        }

        $log[] = '[OK] Rozpakowano ' . $extracted . ' plików, błędów: ' . $errors;
        $zip->close();

        if ( @unlink( $fileName ) ) {
            $log[] = '[OK] Usunięto plik update.zip';
        }

        return $log;
    }

    private function saveLog( array $log ): void
    {
        @file_put_contents( '../libraries/update_log.txt', implode( "\n", $log ) );
    }

    /**
     * Wykonuje zaległe migracje z tabeli pp_updates.
     */
    public function runPendingMigrations(): void
    {
        $results = $this->db->select( 'pp_updates', [ 'name' ], [ 'done' => 0 ] );
        if ( !is_array( $results ) ) {
            return;
        }

        foreach ( $results as $row ) {
            $method = $row['name'];
            if ( method_exists( $this, $method ) ) {
                $this->$method();
            }
        }
    }

    public function update0197(): void
    {
        $rows = $this->db->select( 'pp_shop_order_products', [ 'id', 'product_id' ], [ 'parent_product_id' => null ] );

        if ( is_array( $rows ) ) {
            foreach ( $rows as $row ) {
                $parentId = $this->db->get( 'pp_shop_products', 'parent_id', [ 'id' => $row['product_id'] ] );
                $this->db->update( 'pp_shop_order_products', [
                    'parent_product_id' => $parentId ?: $row['product_id'],
                ], [ 'id' => $row['id'] ] );
            }
        }

        $this->db->update( 'pp_updates', [ 'done' => 1 ], [ 'name' => 'update0197' ] );
    }
}
