//
//  RealNameAuthViewModelTests.swift
//  suixinkanTests
//

import UIKit
import XCTest
@testable import suixinkan

/// 实名认证 ViewModel 测试。
@MainActor
final class RealNameAuthViewModelTests: XCTestCase {
    func testNewApplicationStartsAtIdentityStepAndRequiresName() {
        let viewModel = RealNameAuthViewModel()

        XCTAssertEqual(viewModel.mode, .newApplication)
        XCTAssertEqual(viewModel.currentStep, .identity)
        XCTAssertFalse(viewModel.isAgreed)
        XCTAssertFalse(viewModel.canGoNextStep)
        XCTAssertEqual(viewModel.validationMessage, "请输入真实姓名")
    }

    func testInputFilteringMatchesFormRules() {
        let viewModel = RealNameAuthViewModel()

        viewModel.updateIdCardNo("11010519491231002xABC")
        viewModel.updateSmsCode("1a2b345678901234")

        XCTAssertEqual(viewModel.idCardNo, "11010519491231002X")
        XCTAssertEqual(viewModel.smsCode, "12345678901")
    }

    func testModesFollowAuditStatus() throws {
        XCTAssertEqual(RealNameAuthViewModel(info: try makeInfo(status: 1)).mode, .pending)
        XCTAssertEqual(RealNameAuthViewModel(info: try makeInfo(status: 2)).mode, .approved)
        XCTAssertEqual(RealNameAuthViewModel(info: try makeInfo(status: 3)).mode, .rejected)
    }

    func testRejectedInfoIsEditableAndCanAdvance() throws {
        let viewModel = RealNameAuthViewModel(info: try makeInfo(status: 3))

        XCTAssertTrue(viewModel.isAgreed)
        XCTAssertTrue(viewModel.canGoNextStep)
        XCTAssertNil(viewModel.goToVerificationStep())
        XCTAssertEqual(viewModel.currentStep, .verification)
        XCTAssertFalse(viewModel.canSubmit)
    }

    func testReadOnlyInfoCanBrowseSecondStepButCannotSubmit() throws {
        let viewModel = RealNameAuthViewModel(info: try makeInfo(status: 2))

        XCTAssertNil(viewModel.goToVerificationStep())
        XCTAssertEqual(viewModel.currentStep, .verification)
        XCTAssertFalse(viewModel.canSubmit)
        viewModel.updateRealName("不能修改")
        XCTAssertEqual(viewModel.realName, "张三")
    }

    func testLongValidityClearsEndDate() throws {
        let viewModel = RealNameAuthViewModel(info: try makeInfo(status: 3))
        XCTAssertNotNil(viewModel.endDate)

        viewModel.updateLongValid(true)

        XCTAssertTrue(viewModel.isLongValid)
        XCTAssertNil(viewModel.endDate)
        XCTAssertTrue(viewModel.canGoNextStep)
    }

    func testEndDateCannotBeEarlierThanStartDate() throws {
        let viewModel = RealNameAuthViewModel(info: try makeInfo(status: 3))

        viewModel.updateEndDate(Self.date("2026-07-12"))

        XCTAssertFalse(viewModel.canGoNextStep)
        XCTAssertEqual(viewModel.validationMessage, "证件结束日期不能早于起始日期")
    }

    func testAgreementControlsStepTransition() throws {
        let viewModel = RealNameAuthViewModel(info: try makeInfo(status: 3))
        XCTAssertTrue(viewModel.canGoNextStep)

        viewModel.toggleAgreement()

        XCTAssertFalse(viewModel.canGoNextStep)
        XCTAssertEqual(viewModel.goToVerificationStep(), "请阅读并同意用户协议与隐私政策")
        XCTAssertEqual(viewModel.currentStep, .identity)
    }

    func testImmediateUploadsProduceURLsAndEnableNextStep() async throws {
        let viewModel = RealNameAuthViewModel()
        viewModel.updateRealName("张三")
        viewModel.updateIdCardNo("11010519491231002X")
        viewModel.updateStartDate(Self.date("2026-07-13"))
        viewModel.updateEndDate(Self.date("2036-07-13"))
        viewModel.toggleAgreement()
        let imageData = try XCTUnwrap(Self.imageData())
        let uploader = MockRealNameOSSUploadService()

        try await viewModel.uploadIdentityImage(data: imageData, side: .back, uploader: uploader, scenicId: 9, timestamp: 1)
        try await viewModel.uploadIdentityImage(data: imageData, side: .front, uploader: uploader, scenicId: 9, timestamp: 2)

        XCTAssertEqual(viewModel.backUrl, "https://cdn.example.com/real_name_back_1.jpg")
        XCTAssertEqual(viewModel.frontUrl, "https://cdn.example.com/real_name_front_2.jpg")
        XCTAssertEqual(viewModel.backUploadProgress, 100)
        XCTAssertTrue(viewModel.canGoNextStep)

        viewModel.deleteIdentityImage(side: .front)
        XCTAssertTrue(viewModel.frontUrl.isEmpty)
        XCTAssertFalse(viewModel.canGoNextStep)
    }

    func testUploadFailureClearsPendingPreviewAndURL() async throws {
        let viewModel = RealNameAuthViewModel()
        let imageData = try XCTUnwrap(Self.imageData())

        do {
            try await viewModel.uploadIdentityImage(
                data: imageData,
                side: .front,
                uploader: MockRealNameOSSUploadService(shouldFail: true),
                scenicId: 9
            )
            XCTFail("Expected upload failure")
        } catch {
            XCTAssertTrue(viewModel.frontUrl.isEmpty)
            XCTAssertNil(viewModel.frontPreviewImageData)
            XCTAssertFalse(viewModel.isFrontUploading)
        }
    }

    func testRequestUsesNormalizedFieldsAndLongTermDate() throws {
        let viewModel = RealNameAuthViewModel(info: try makeInfo(status: 3))
        viewModel.updateLongValid(true)
        viewModel.updateSmsCode("12a3456")
        _ = viewModel.goToVerificationStep()

        let request = try viewModel.makeRequest()

        XCTAssertEqual(request.idCardNo, "11010519491231002X")
        XCTAssertEqual(request.smsVerifyCode, "123456")
        XCTAssertEqual(request.isLongValid, 1)
        XCTAssertEqual(request.endDate, "")
    }

    func testSendCodeStartsSixtySecondCountdown() async throws {
        let viewModel = RealNameAuthViewModel(info: try makeInfo(status: 3))
        _ = viewModel.goToVerificationStep()
        let response = Data(#"{"code":100000,"msg":"success","data":{}}"#.utf8)
        let api = ProfileAPI(client: APIClient(environment: .testing, session: MockURLSession(responses: [response])))

        try await viewModel.sendCode(api: api)

        XCTAssertEqual(viewModel.otpCountdown, 60)
        XCTAssertFalse(viewModel.canRequestCode)
        XCTAssertEqual(viewModel.statusMessage, "验证码发送成功")
    }

    func testIsValidMainlandIDCardNumberChecksChecksum() {
        XCTAssertTrue(RealNameAuthViewModel.isValidMainlandIDCardNumber("11010519491231002X"))
        XCTAssertFalse(RealNameAuthViewModel.isValidMainlandIDCardNumber("123"))
    }

    func testNormalizedIDCardNumberUppercasesSuffix() {
        XCTAssertEqual(
            RealNameAuthViewModel.normalizedIDCardNumber("11010519491231002x"),
            "11010519491231002X"
        )
    }

    private func makeInfo(status: Int) throws -> RealNameInfo {
        let json = Data(
            """
            {
              "real_name":"张三",
              "id_card_no":"11010519491231002X",
              "audit_status":\(status),
              "audit_status_text":"审核状态",
              "start_date":"2026-07-13",
              "end_date":"2036-07-13",
              "is_long_valid":0,
              "front_url":"https://cdn.example.com/front.jpg",
              "back_url":"https://cdn.example.com/back.jpg",
              "auditor":{"id":1,"name":"审核员"},
              "audit_at":"2026-07-13 12:00:00",
              "reject_reason":"照片模糊"
            }
            """.utf8
        )
        return try JSONDecoder().decode(RealNameInfo.self, from: json)
    }

    private static func date(_ value: String) -> Date {
        let formatter = DateFormatter()
        formatter.calendar = Calendar(identifier: .gregorian)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter.date(from: value)!
    }

    private static func imageData() -> Data? {
        let renderer = UIGraphicsImageRenderer(size: CGSize(width: 40, height: 24))
        let image = renderer.image { context in
            UIColor.white.setFill()
            context.fill(CGRect(x: 0, y: 0, width: 40, height: 24))
        }
        return image.jpegData(compressionQuality: 0.9)
    }
}

/// 实名认证上传测试替身。
@MainActor
private struct MockRealNameOSSUploadService: OSSUploadServing {
    let shouldFail: Bool

    init(shouldFail: Bool = false) {
        self.shouldFail = shouldFail
    }

    func uploadUserAvatar(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
        "https://cdn.example.com/avatar.jpg"
    }

    func uploadRealNameImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
        if shouldFail { throw URLError(.cannotWriteToFile) }
        onProgress(100)
        return "https://cdn.example.com/\(fileName)"
    }

    func uploadBankCardImage(data: Data, fileName: String, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
        "https://cdn.example.com/bank.jpg"
    }
}
