# ftp.test --
#
# Tests FTP transfers and related FTP-specific options.
#
# Copyright (c) 2024-2026 Massimo Manghi
#
# SPDX-License-Identifier: TCL
#
# See the file "license.terms" at the top level of this distribution
# for information on usage and redistribution of this file, and for the
# complete disclaimer of warranties and limitation of liability.
#
# Portions of this file were drafted or revised with AI assistance
# under the direction and review of Massimo Manghi

package require tcltest
package require sha256
namespace import ::tcltest::*

source [file join [file dirname [file normalize [info script]]] support.tcl]

::tclcurl::test::load_package

proc ::tclcurl::test::file_sha256_hex {path} {
    set fh [open $path rb]
    try {
        return [::sha2::sha256 -hex [read $fh]]
    } finally {
        close $fh
    }
}

proc ::tclcurl::test::seed_ftp_file {source_path target_name} {
    file mkdir [::tclcurl::test::ftp_root]
    file copy -force $source_path [file join [::tclcurl::test::ftp_root] $target_name]
}

proc ::tclcurl::test::write_binary_file {path data} {
    set fh [open $path wb]
    try {
        puts -nonewline $fh $data
    } finally {
        close $fh
    }
}

test ftp-1.1 {-upload stores README.md on the FTP server} -constraints ftp_server -body {
    set readme [file join [::tclcurl::test::repo_root] README.md]
    ::tclcurl::test::ftp_reset_root
    ::tclcurl::test::with_easy_handle handle {
        $handle configure -url [::tclcurl::test::server::ftp_base_url README.md] \
                          -upload 1 \
                          -infile $readme \
                          -infilesize [file size $readme] \
                          -noprogress 1
        set rc [$handle perform]
        set uploaded [file join [::tclcurl::test::ftp_root] README.md]
        list $rc \
             [file exists $uploaded] \
             [expr {[::tclcurl::test::file_sha256_hex $uploaded] eq [::tclcurl::test::file_sha256_hex $readme]}]
    }
} -result {0 1 1}

test ftp-1.2 {-upload stores a binary file on the FTP server} -constraints ftp_server -body {
    set fixture [file join [file dirname [file normalize [info script]]] tcl9.png]
    ::tclcurl::test::ftp_reset_root
    ::tclcurl::test::with_easy_handle handle {
        $handle configure -url [::tclcurl::test::server::ftp_base_url tcl9.png] \
                          -upload 1 \
                          -infile $fixture \
                          -infilesize [file size $fixture] \
                          -noprogress 1
        set rc [$handle perform]
        set uploaded [file join [::tclcurl::test::ftp_root] tcl9.png]
        list $rc \
             [file exists $uploaded] \
             [expr {[::tclcurl::test::file_sha256_hex $uploaded] eq [::tclcurl::test::file_sha256_hex $fixture]}]
    }
} -result {0 1 1}

test ftp-1.3 {-ftplistonly returns uploaded files from the remote directory} -constraints ftp_server -body {
    set body {}
    set readme [file join [::tclcurl::test::repo_root] README.md]
    set fixture [file join [file dirname [file normalize [info script]]] tcl9.png]
    ::tclcurl::test::ftp_reset_root
    ::tclcurl::test::seed_ftp_file $readme README.md
    ::tclcurl::test::seed_ftp_file $fixture tcl9.png
    ::tclcurl::test::with_easy_handle handle {
        $handle configure -url [::tclcurl::test::server::ftp_base_url] \
                          -ftplistonly 1 \
                          -bodyvar body \
                          -noprogress 1
        set rc [$handle perform]
        set listing [split [::tclcurl::test::normalizedBody $body] "\n"]
        list $rc \
             [expr {[lsearch -exact $listing README.md] >= 0}] \
             [expr {[lsearch -exact $listing tcl9.png] >= 0}]
    }
} -result {0 1 1}

test ftp-1.4 {download retrieves README.md from the FTP server} -constraints ftp_server -body {
    set body {}
    set readme [file join [::tclcurl::test::repo_root] README.md]
    ::tclcurl::test::ftp_reset_root
    ::tclcurl::test::seed_ftp_file $readme README.md
    ::tclcurl::test::with_easy_handle handle {
        $handle configure -url [::tclcurl::test::server::ftp_base_url README.md] \
                          -bodyvar body \
                          -noprogress 1
        set rc [$handle perform]
        list $rc \
             [$handle getinfo responsecode] \
             [expr {[::sha2::sha256 -hex $body] eq [::tclcurl::test::file_sha256_hex $readme]}]
    }
} -result {0 226 1}

test ftp-1.5 {download retrieves a binary file from the FTP server} -constraints ftp_server -body {
    set body {}
    set fixture [file join [file dirname [file normalize [info script]]] tcl9.png]
    ::tclcurl::test::ftp_reset_root
    ::tclcurl::test::seed_ftp_file $fixture tcl9.png
    ::tclcurl::test::with_easy_handle handle {
        $handle configure -url [::tclcurl::test::server::ftp_base_url tcl9.png] \
                          -bodyvar body \
                          -noprogress 1
        set rc [$handle perform]
        list $rc \
             [$handle getinfo responsecode] \
             [expr {[::sha2::sha256 -hex $body] eq [::tclcurl::test::file_sha256_hex $fixture]}]
    }
} -result {0 226 1}

test ftp-2.1 {-dirlistonly returns names only for an FTP directory URL} -constraints ftp_server -body {
    set body {}
    set readme [file join [::tclcurl::test::repo_root] README.md]
    set fixture [file join [file dirname [file normalize [info script]]] tcl9.png]
    ::tclcurl::test::ftp_reset_root
    ::tclcurl::test::seed_ftp_file $readme README.md
    ::tclcurl::test::seed_ftp_file $fixture tcl9.png
    ::tclcurl::test::with_easy_handle handle {
        $handle configure -url [::tclcurl::test::server::ftp_base_url] \
                          -dirlistonly 1 \
                          -bodyvar body \
                          -noprogress 1
        set rc [$handle perform]
        set listing [split [::tclcurl::test::normalizedBody $body] "\n"]
        list $rc \
             [expr {[lsearch -exact $listing README.md] >= 0}] \
             [expr {[lsearch -exact $listing tcl9.png] >= 0}] \
             [expr {[string first "owner group" $body] < 0}]
    }
} -result {0 1 1 1}

test ftp-2.2 {-dirlistonly implies a directory listing without a trailing slash} -constraints ftp_server -body {
    set body {}
    set readme [file join [::tclcurl::test::repo_root] README.md]
    set fixture [file join [file dirname [file normalize [info script]]] tcl9.png]
    ::tclcurl::test::ftp_reset_root
    ::tclcurl::test::seed_ftp_file $readme README.md
    ::tclcurl::test::seed_ftp_file $fixture tcl9.png
    ::tclcurl::test::with_easy_handle handle {
        $handle configure -url [string trimright [::tclcurl::test::server::ftp_base_url] /] \
                          -dirlistonly 1 \
                          -bodyvar body \
                          -noprogress 1
        set rc [$handle perform]
        set listing [split [::tclcurl::test::normalizedBody $body] "\n"]
        list $rc \
             [expr {[lsearch -exact $listing README.md] >= 0}] \
             [expr {[lsearch -exact $listing tcl9.png] >= 0}]
    }
} -result {0 1 1}

test ftp-3.1 {-quote can create and enter a subdirectory before uploading a file} -constraints ftp_server -body {
    set library [lindex [::tclcurl::test::build_library_candidates] 0]
    if {$library eq {}} {
        return -code error "no built TclCurl library available for ftp quote test"
    }
    ::tclcurl::test::ftp_reset_root
    ::tclcurl::test::with_easy_handle handle {
        $handle configure -url [::tclcurl::test::server::ftp_base_url [file tail $library]] \
                          -upload 1 \
                          -infile $library \
                          -infilesize [file size $library] \
                          -quote [list "MKD uploads" "CWD uploads"] \
                          -noprogress 1
        set rc [$handle perform]
        set uploaded [file join [::tclcurl::test::ftp_root] uploads [file tail $library]]
        list $rc \
             [file isdirectory [file join [::tclcurl::test::ftp_root] uploads]] \
             [file exists $uploaded] \
             [expr {[::tclcurl::test::file_sha256_hex $uploaded] eq [::tclcurl::test::file_sha256_hex $library]}]
    }
} -result {0 1 1 1}

test ftp-3.2 {-quote can change directory before download and -postquote can delete the remote file} -constraints ftp_server -body {
    set body {}
    set library [lindex [::tclcurl::test::build_library_candidates] 0]
    if {$library eq {}} {
        return -code error "no built TclCurl library available for ftp quote test"
    }
    ::tclcurl::test::ftp_reset_root
    file mkdir [file join [::tclcurl::test::ftp_root] uploads]
    file copy -force $library [file join [::tclcurl::test::ftp_root] uploads [file tail $library]]
    ::tclcurl::test::with_easy_handle handle {
        $handle configure -url [::tclcurl::test::server::ftp_base_url [file tail $library]] \
                          -quote [list "CWD uploads"] \
                          -postquote [list "DELE [file tail $library]"] \
                          -bodyvar body \
                          -noprogress 1
        set rc [$handle perform]
        set remote_copy [file join [::tclcurl::test::ftp_root] uploads [file tail $library]]
        list $rc \
             [expr {[::sha2::sha256 -hex $body] eq [::tclcurl::test::file_sha256_hex $library]}] \
             [expr {![file exists $remote_copy]}] \
             [file isdirectory [file join [::tclcurl::test::ftp_root] uploads]]
    }
} -result {0 1 1 1}

test ftp-4.1 {upload to a missing FTP subdirectory fails without -ftpcreatemissingdirs} -constraints ftp_server -body {
    set errbuf {}
    set readme [file join [::tclcurl::test::repo_root] README.md]
    set remote_path "missing/subdir/README.md"
    ::tclcurl::test::ftp_reset_root
    ::tclcurl::test::with_easy_handle handle {
        $handle configure -url [::tclcurl::test::server::ftp_base_url $remote_path] \
                          -upload 1 \
                          -infile $readme \
                          -infilesize [file size $readme] \
                          -errorbuffer errbuf \
                          -noprogress 1
        list [catch {$handle perform}] \
             [expr {[string length $errbuf] > 0}] \
             [expr {![file exists [file join [::tclcurl::test::ftp_root] missing subdir README.md]]}]
    }
} -result {1 1 1}

test ftp-4.2 {-ftpcreatemissingdirs creates remote directories before upload} -constraints ftp_server -body {
    set readme [file join [::tclcurl::test::repo_root] README.md]
    set remote_path "missing/subdir/README.md"
    ::tclcurl::test::ftp_reset_root
    ::tclcurl::test::with_easy_handle handle {
        $handle configure -url [::tclcurl::test::server::ftp_base_url $remote_path] \
                          -upload 1 \
                          -infile $readme \
                          -infilesize [file size $readme] \
                          -ftpcreatemissingdirs 1 \
                          -noprogress 1
        set rc [$handle perform]
        set uploaded [file join [::tclcurl::test::ftp_root] missing subdir README.md]
        list $rc \
             [file isdirectory [file join [::tclcurl::test::ftp_root] missing]] \
             [file isdirectory [file join [::tclcurl::test::ftp_root] missing subdir]] \
             [file exists $uploaded] \
             [expr {[::tclcurl::test::file_sha256_hex $uploaded] eq [::tclcurl::test::file_sha256_hex $readme]}]
    }
} -result {0 1 1 1 1}

test ftp-4.3 {-filetime retrieves the remote modification time for an FTP file} -constraints ftp_server -body {
    set body {}
    set remote_name filetime-fixture.txt
    set temp_path [file join [::tcltest::temporaryDirectory] $remote_name]
    set payload "filetime fixture\n"
    set expected_mtime 1735689600
    ::tclcurl::test::write_binary_file $temp_path $payload
    file mtime $temp_path $expected_mtime
    ::tclcurl::test::ftp_reset_root
    ::tclcurl::test::seed_ftp_file $temp_path $remote_name
    file mtime [file join [::tclcurl::test::ftp_root] $remote_name] $expected_mtime
    ::tclcurl::test::with_easy_handle handle {
        $handle configure -url [::tclcurl::test::server::ftp_base_url $remote_name] \
                          -filetime 1 \
                          -bodyvar body \
                          -noprogress 1
        set rc [$handle perform]
        list $rc \
             [$handle getinfo responsecode] \
             [$handle getinfo filetime] \
             [expr {$body eq $payload}]
    }
} -cleanup {
    catch {file delete -force [file join [::tcltest::temporaryDirectory] filetime-fixture.txt]}
} -result [list 0 226 1735689600 1]

test ftp-5.1 {-resumefrom downloads the remaining tail of an FTP file} -constraints ftp_server -body {
    set body {}
    set payload [string range [::tclcurl::test::range_fixture] 0 4095]
    set resume_from 257
    set remote_name resumefrom-fixture.txt
    set temp_path [file join [::tcltest::temporaryDirectory] $remote_name]
    ::tclcurl::test::write_binary_file $temp_path $payload
    ::tclcurl::test::ftp_reset_root
    ::tclcurl::test::seed_ftp_file $temp_path $remote_name
    ::tclcurl::test::with_easy_handle handle {
        $handle configure -url [::tclcurl::test::server::ftp_base_url $remote_name] \
                          -resumefrom $resume_from \
                          -bodyvar body \
                          -noprogress 1
        set rc [$handle perform]
        list $rc \
             [$handle getinfo responsecode] \
             [string index $body 0] \
             [expr {$body eq [string range $payload $resume_from end]}]
    }
} -cleanup {
    catch {file delete -force [file join [::tcltest::temporaryDirectory] resumefrom-fixture.txt]}
} -result [list 0 226 [string index [::tclcurl::test::range_fixture] 257] 1]

test ftp-5.2 {-resumefrom rejects malformed values} -constraints ftp_server -body {
    ::tclcurl::test::with_easy_handle handle {
        set status [catch {
                  $handle configure -url [::tclcurl::test::server::ftp_base_url resumefrom-fixture.txt] \
                                    -resumefrom bogus \
                                    -noprogress 1
              } result]
        list $status [string match {setting option -resumefrom:*} $result]
    }
} -result {1 1}

cleanupTests
