//
//  EditPictureControlTests.swift
//  damus
//
//  Created by Daniel D'Aquino on 2024-12-28.
//

import XCTest
import SnapshotTesting
@testable import damus
import SwiftUI

final class EditPictureControlTests: XCTestCase {
    typealias ViewModel = EditPictureControlViewModel<MockImageUploadModel>
    typealias SelectionState = ViewModel.PictureSelectionState
    
    let mock_uploader = MockMediaUploader()
    let mock_url = URL(string: get_test_uploaded_url())!
    let test_image = UIImage(named: "bitcoin-p2p")!
    let mock_keypair = test_keypair
    let mock_pubkey = test_keypair.pubkey
    
    override func setUp() {
        super.setUp()
    }
    
    @MainActor
    func testPFPLibrarySelection() async {
        let expectation = XCTestExpectation(description: "Received URL")
        let view_model = ViewModel(
            context: .profile_picture,
            pubkey: mock_pubkey,
            current_image_url: .constant(mock_url),
            state: .ready,
            keypair: mock_keypair,
            uploader: mock_uploader,
            callback: { url in
                XCTAssertEqual(url, URL(string: get_test_uploaded_url()))
                expectation.fulfill()
            }
        )
        
        XCTAssertEqual(view_model.state.step, SelectionState.Step.ready)
        
        view_model.select_image_from_library()
        XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_library)
        
        view_model.request_upload_authorization(.uiimage(test_image))
        XCTAssertEqual(view_model.state.step, SelectionState.Step.confirming_upload)
        
        view_model.confirm_upload_authorization()
        XCTAssertEqual(view_model.state.step, SelectionState.Step.cropping)
        
        view_model.finished_cropping(croppedImage: test_image.resized(to: CGSize(width: 10, height: 10)))
        XCTAssertEqual(view_model.state.step, SelectionState.Step.uploading)
        
        // Wait to receive URL
        await fulfillment(of: [expectation], timeout: 5)
        XCTAssertEqual(view_model.state.step, SelectionState.Step.ready)
    }
    
    @MainActor
    func testBannerLibrarySelection() async {
        let expectation = XCTestExpectation(description: "Received URL")
        let view_model = ViewModel(
            context: .normal,
            pubkey: mock_pubkey,
            current_image_url: .constant(mock_url),
            state: .ready,
            keypair: mock_keypair,
            uploader: mock_uploader,
            callback: { url in
                XCTAssertEqual(url, URL(string: get_test_uploaded_url()))
                expectation.fulfill()
            }
        )
        
        XCTAssertEqual(view_model.state.step, SelectionState.Step.ready)
        
        view_model.select_image_from_library()
        XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_library)
        
        let test_image = UIImage(named: "bitcoin-p2p")!
        view_model.request_upload_authorization(.uiimage(test_image))
        XCTAssertEqual(view_model.state.step, SelectionState.Step.confirming_upload)
        
        view_model.confirm_upload_authorization()
        XCTAssertEqual(view_model.state.step, SelectionState.Step.uploading)
        
        // Wait to receive URL
        await fulfillment(of: [expectation], timeout: 5)
        XCTAssertEqual(view_model.state.step, SelectionState.Step.ready)
    }
    
    @MainActor
    func testPFPCameraSelection() async {
        let expectation = XCTestExpectation(description: "Received URL")
        let view_model = ViewModel(
            context: .profile_picture,
            pubkey: mock_pubkey,
            current_image_url: .constant(mock_url),
            state: .ready,
            keypair: mock_keypair,
            uploader: mock_uploader,
            callback: { url in
                XCTAssertEqual(url, URL(string: get_test_uploaded_url()))
                expectation.fulfill()
            }
        )
        
        // Ready
        XCTAssertEqual(view_model.state.step, SelectionState.Step.ready)
        
        // Take picture
        view_model.select_image_from_camera()
        XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_camera)
        XCTAssertEqual(view_model.state.show_camera, true)
        
        // Confirm upload
        view_model.request_upload_authorization(.uiimage(test_image))
        XCTAssertEqual(view_model.state.step, SelectionState.Step.confirming_upload)
        XCTAssertEqual(view_model.state.is_confirming_upload, true)
        XCTAssertEqual(view_model.state.show_camera, false)
        
        // Confirm and crop
        view_model.confirm_upload_authorization()
        XCTAssertEqual(view_model.state.step, SelectionState.Step.cropping)
        XCTAssertEqual(view_model.state.show_image_cropper, true)
        XCTAssertEqual(view_model.state.is_confirming_upload, false)
        
        // Finish cropping and upload
        view_model.finished_cropping(croppedImage: test_image.resized(to: CGSize(width: 10, height: 10)))
        XCTAssertEqual(view_model.state.step, SelectionState.Step.uploading)
        XCTAssertEqual(view_model.state.show_image_cropper, false)
        
        // Wait to receive URL
        await fulfillment(of: [expectation], timeout: 5)
        XCTAssertEqual(view_model.state.step, SelectionState.Step.ready)
    }
    
    @MainActor
    func testBannerCameraSelection() async {
        let expectation = XCTestExpectation(description: "Received URL")
        let view_model = ViewModel(
            context: .normal,
            pubkey: mock_pubkey,
            current_image_url: .constant(mock_url),
            state: .ready,
            keypair: mock_keypair,
            uploader: mock_uploader,
            callback: { url in
                XCTAssertEqual(url, URL(string: get_test_uploaded_url()))
                expectation.fulfill()
            }
        )
        
        // Ready
        XCTAssertEqual(view_model.state.step, SelectionState.Step.ready)
        
        // Take picture
        view_model.select_image_from_camera()
        XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_camera)
        XCTAssertEqual(view_model.state.show_camera, true)
        
        // Confirm upload
        view_model.request_upload_authorization(.uiimage(test_image))
        XCTAssertEqual(view_model.state.step, SelectionState.Step.confirming_upload)
        XCTAssertEqual(view_model.state.is_confirming_upload, true)
        XCTAssertEqual(view_model.state.show_camera, false)
        
        // Confirm and upload
        view_model.confirm_upload_authorization()
        XCTAssertEqual(view_model.state.step, SelectionState.Step.uploading)
        XCTAssertEqual(view_model.state.show_image_cropper, false)
        XCTAssertEqual(view_model.state.is_confirming_upload, false)
        
        // Wait to receive URL
        await fulfillment(of: [expectation], timeout: 5)
        XCTAssertEqual(view_model.state.step, SelectionState.Step.ready)
    }
    
    @MainActor
    func testPFPUrlSelection() async {
        let expectation = XCTestExpectation(description: "Received URL")
        let view_model = ViewModel(
            context: .profile_picture,
            pubkey: mock_pubkey,
            current_image_url: .constant(mock_url),
            state: .ready,
            keypair: mock_keypair,
            uploader: mock_uploader,
            callback: { url in
                if url == self.mock_url {
                    expectation.fulfill()
                }
            }
        )
        
        XCTAssertEqual(view_model.state.step, SelectionState.Step.ready)
        
        view_model.select_image_from_url()
        XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_url)
        
        view_model.choose_url(mock_url)
        XCTAssertEqual(view_model.state.step, SelectionState.Step.ready)
        let current_image_url = view_model.current_image_url
        XCTAssertEqual(current_image_url, mock_url)
        
        // Wait to receive URL
        await fulfillment(of: [expectation], timeout: 5)
    }
    
    @MainActor
    func testPFPSelectionWithCancellation() async {
        let expectation = XCTestExpectation(description: "Received URL")
        let view_model = ViewModel(
            context: .profile_picture,
            pubkey: mock_pubkey,
            current_image_url: .constant(mock_url),
            state: .ready,
            keypair: mock_keypair,
            uploader: mock_uploader,
            callback: { url in
                XCTAssertEqual(url, URL(string: get_test_uploaded_url()))
                expectation.fulfill()
            }
        )
        
        XCTAssertEqual(view_model.state.step, SelectionState.Step.ready)
        
        // Open camera
        view_model.select_image_from_camera()
        XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_camera)
        XCTAssertTrue(view_model.show_camera.wrappedValue)
        
        // Dismiss camera
        view_model.show_camera.wrappedValue = false
        XCTAssertFalse(view_model.show_camera.wrappedValue)
        XCTAssertEqual(view_model.state.step, SelectionState.Step.ready)
        
        // Open library
        view_model.select_image_from_library()
        XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_library)
        XCTAssertTrue(view_model.show_library.wrappedValue)
        
        // Dismiss library
        view_model.show_library.wrappedValue = false
        XCTAssertFalse(view_model.show_library.wrappedValue)
        XCTAssertEqual(view_model.state.step, SelectionState.Step.ready)
        
        // Select from URL
        view_model.select_image_from_url()
        XCTAssertEqual(view_model.state.step, SelectionState.Step.selecting_picture_from_url)
        XCTAssertTrue(view_model.show_url_sheet.wrappedValue)
        
        // Dismiss URL sheet
        view_model.show_url_sheet.wrappedValue = false
        XCTAssertFalse(view_model.show_url_sheet.wrappedValue)
        XCTAssertEqual(view_model.state.step, SelectionState.Step.ready)
        
        // Select from library and start cropping
        view_model.select_image_from_library()
        view_model.request_upload_authorization(.uiimage(test_image))
        view_model.confirm_upload_authorization()
        XCTAssertEqual(view_model.state.step, SelectionState.Step.cropping)
        XCTAssertTrue(view_model.show_image_cropper.wrappedValue)
        
        // Cancel during cropping
        view_model.show_image_cropper.wrappedValue = false
        XCTAssertEqual(view_model.state.step, SelectionState.Step.ready)
    }
    
    /*
    @MainActor
    func testEditPictureControlFirstTimeSetup() async {
        var current_image_url: URL? = nil
        
        let view_model = EditPictureControl.Model(
            context: .profile_picture,
            pubkey: mock_pubkey,
            current_image_url: Binding(get: { return current_image_url }, set: { current_image_url = $0 }),
            state: .ready,
            keypair: mock_keypair,
            uploader: mock_uploader,
            callback: { url in
                return
            }
        )
        
        // Setup the test view
        let test_view = EditPictureControl(
            model: view_model,
            style: .init(size: 25, first_time_setup: true),
            callback: { url in return }
        )
        let hostView = UIHostingController(rootView: test_view)
        
        sleep(2)    // Wait a bit for things to load
        assertSnapshot(matching: hostView, as: .image(on: .iPhoneSe(.portrait)))
    }
    
    @MainActor
    func testEditPictureControlNotFirstTimeSetup() async {
        var current_image_url: URL? = nil
        
        let view_model = EditPictureControl.Model(
            context: .profile_picture,
            pubkey: mock_pubkey,
            current_image_url: Binding(get: { return current_image_url }, set: { current_image_url = $0 }),
            state: .ready,
            keypair: mock_keypair,
            uploader: mock_uploader,
            callback: { url in
                return
            }
        )
        
        // Setup the test view
        let test_view = EditPictureControl(
            model: view_model,
            style: .init(size: nil, first_time_setup: false),
            callback: { url in return }
        )
        let hostView = UIHostingController(rootView: test_view)
        
        sleep(2)    // Wait a bit for things to load
        assertSnapshot(matching: hostView, as: .image(on: .iPhoneSe(.portrait)))
    }
    */
    
    // MARK: Mock classes
    
    class MockMediaUploader: MediaUploaderProtocol {
        var nameParam: String { return "name_param" }
        var mediaTypeParam: String { return "media_type_param" }
        var supportsVideo: Bool { return true }
        var requiresNip98: Bool { return true }
        var postAPI: String { return "http://localhost:8000" }
        
        func getMediaURL(from data: Data) -> String? {
            return "http://localhost:8000"
        }
        
        func mediaTypeValue(for mediaType: damus.ImageUploadMediaType) -> String? {
            return "media_type_value"
        }
        
        var uploadCalled = false
        var uploadCompletion: (() -> Void)?
    }
    
    class MockImageUploadModel: ImageUploadModelProtocol {
        required init() {}
        
        func start(media: damus.MediaUpload, uploader: any damus.MediaUploaderProtocol, mediaType: damus.ImageUploadMediaType, keypair: damus.Keypair?) async -> damus.ImageUploadResult {
            return damus.ImageUploadResult.success(get_test_uploaded_url())
        }
    }
}

fileprivate func get_test_uploaded_url() -> String {
    return "https://example.com/newimage.jpg"
}

fileprivate extension UIImage {
    static func from(url: URL) throws -> UIImage? {
        let data = try Data(contentsOf: url)
        return UIImage(data: data)
    }
}
