//
//  OperatingAreaTests.swift
//  suixinkanTests
//
//  Created by Codex on 2026/6/25.
//

import XCTest
@testable import suixinkan

@MainActor
/// 运营区域测试，覆盖接口编码、角色分流和围栏解析。
final class OperatingAreaTests: XCTestCase {
    func testAPIsUseExpectedQueriesAndDecodeFixture() async throws {
        let session = OperatingRecordingURLSession(data: try TestFixture.data(named: "operating_area_success"))
        let api = OperatingAreaAPI(client: APIClient(session: session))

        let storeResponse = try await api.storeBusinessArea(storeId: 101)
        XCTAssertEqual(storeResponse.total, 4)
        XCTAssertEqual(storeResponse.list.first?.id, 101)
        var request = try XCTUnwrap(session.requests.last)
        XCTAssertEqual(request.httpMethod, "GET")
        XCTAssertEqual(request.url?.path, "/api/app/store/business-area")
        XCTAssertEqual(queryItems(from: request)["store_id"], "101")

        _ = try await api.scenicAdminBusinessArea(scenicId: 88)
        request = try XCTUnwrap(session.requests.last)
        XCTAssertEqual(request.url?.path, "/api/app/scenic-admin/business-area")
        XCTAssertEqual(queryItems(from: request)["scenic_id"], "88")
    }

    func testViewModelUsesStoreAndScenicModes() async {
        let api = OperatingMock()
        api.response = ListPayload(total: 1, list: [area(id: 101)])

        let storeViewModel = OperatingAreaViewModel()
        await storeViewModel.reload(api: api, accountContext: accountContext(storeId: 101), permissionContext: permissionContext(roleName: "店铺管理员"))
        XCTAssertEqual(api.storeIds, [101])
        XCTAssertEqual(storeViewModel.fenceRings.first?.isCurrentStore, true)

        let scenicViewModel = OperatingAreaViewModel()
        await scenicViewModel.reload(api: api, accountContext: accountContext(scenicId: 88, storeId: nil), permissionContext: permissionContext(roleName: "景区管理员"))
        XCTAssertEqual(api.scenicIds, [88])
        XCTAssertEqual(scenicViewModel.mode, .scenicAdmin(scenicId: 88))
    }

    func testViewModelBlocksMissingContextAndFailureClearsData() async {
        let api = OperatingMock()
        let viewModel = OperatingAreaViewModel()

        await viewModel.reload(api: api, accountContext: accountContext(scenicId: nil, storeId: nil), permissionContext: permissionContext(roleName: "店铺管理员"))
        XCTAssertEqual(viewModel.blockReason, .missingStore)
        XCTAssertTrue(api.storeIds.isEmpty)

        api.response = ListPayload(total: 1, list: [area(id: 101)])
        await viewModel.reload(api: api, accountContext: accountContext(storeId: 101), permissionContext: permissionContext(roleName: "店铺管理员"))
        XCTAssertFalse(viewModel.fenceRings.isEmpty)

        api.error = OperatingTestError.sample
        await viewModel.reload(api: api, accountContext: accountContext(storeId: 101), permissionContext: permissionContext(roleName: "店铺管理员"))
        XCTAssertTrue(viewModel.items.isEmpty)
        XCTAssertTrue(viewModel.fenceRings.isEmpty)
        XCTAssertEqual(viewModel.blockReason, .backendMessage("测试错误"))
    }

    func testParserSupportsGeoJSONStringArraysAndFiltersInvalidRings() throws {
        let response = try TestFixture.payload(ListPayload<OperatingAreaItem>.self, named: "operating_area_success")
        let rings = response.list.flatMap { OperatingAreaParser.parseToRings($0.businessMapArea) }

        XCTAssertEqual(rings.count, 3)
        XCTAssertEqual(rings.first?.first, OperatingGeoPoint(latitude: 32.041, longitude: 118.781))
        XCTAssertEqual(rings[1].first, OperatingGeoPoint(latitude: 32.051, longitude: 118.791))
        XCTAssertEqual(rings[2].first, OperatingGeoPoint(latitude: 32.061, longitude: 118.801))
    }

    func testEmptyAndUnparsableAreaStates() async {
        let api = OperatingMock()
        let viewModel = OperatingAreaViewModel()

        api.response = ListPayload(total: 0, list: [])
        await viewModel.reload(api: api, accountContext: accountContext(storeId: 101), permissionContext: permissionContext(roleName: "店铺管理员"))
        XCTAssertEqual(viewModel.blockReason, .emptyAreaList)

        api.response = ListPayload(total: 1, list: [OperatingAreaItem(id: 1, name: "bad", businessMapArea: .array([.array([.number(1), .number(2)])]))])
        await viewModel.reload(api: api, accountContext: accountContext(storeId: 101), permissionContext: permissionContext(roleName: "店铺管理员"))
        XCTAssertEqual(viewModel.blockReason, .noParsableFenceData)
    }
}

@MainActor
private final class OperatingMock: OperatingAreaServing {
    var response = ListPayload<OperatingAreaItem>(total: 0, list: [])
    var error: Error?
    var storeIds: [Int] = []
    var scenicIds: [Int] = []

    func storeBusinessArea(storeId: Int) async throws -> ListPayload<OperatingAreaItem> {
        storeIds.append(storeId)
        if let error { throw error }
        return response
    }

    func scenicAdminBusinessArea(scenicId: Int) async throws -> ListPayload<OperatingAreaItem> {
        scenicIds.append(scenicId)
        if let error { throw error }
        return response
    }
}

private final class OperatingRecordingURLSession: URLSessionProtocol {
    private let data: Data
    private(set) var requests: [URLRequest] = []

    init(data: Data) {
        self.data = data
    }

    func data(for request: URLRequest) async throws -> (Data, URLResponse) {
        requests.append(request)
        return (data, HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!)
    }
}

private enum OperatingTestError: LocalizedError {
    case sample

    var errorDescription: String? { "测试错误" }
}

private func area(id: Int) -> OperatingAreaItem {
    OperatingAreaItem(
        id: id,
        name: "区域\(id)",
        businessMapArea: .array([
            .array([
                .array([.number(118.781), .number(32.041)]),
                .array([.number(118.782), .number(32.041)]),
                .array([.number(118.782), .number(32.042)])
            ])
        ]),
        statusText: "启用",
        typeText: "门店",
        auditStatusText: "通过"
    )
}

@MainActor
private func accountContext(scenicId: Int? = 88, storeId: Int? = 101) -> AccountContext {
    let context = AccountContext()
    let scenics = scenicId.map { [BusinessScope(id: $0, name: "测试景区", kind: .scenic)] } ?? []
    let stores = storeId.map { [BusinessScope(id: $0, name: "测试门店", kind: .store, parentScenicId: scenicId)] } ?? []
    context.replaceScopes(scenic: scenics, stores: stores, currentScenicId: scenicId, currentStoreId: storeId)
    return context
}

@MainActor
private func permissionContext(roleName: String) -> PermissionContext {
    let context = PermissionContext()
    let roleCode = AppRoleCode.fromRoleName(roleName)?.rawValue ?? ""
    let role = RoleInfo(
        id: 1,
        name: roleName,
        roleCode: roleCode,
        permission: [PermissionItem(id: 1, name: "运营区域", uri: "operating-area")]
    )
    context.replaceRolePermissions(
        [RolePermissionResponse(role: role, scenic: [])],
        currentRoleCode: roleCode
    )
    return context
}

private func queryItems(from request: URLRequest) -> [String: String] {
    guard let url = request.url,
          let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
    else { return [:] }
    return Dictionary(uniqueKeysWithValues: (components.queryItems ?? []).compactMap { item in
        item.value.map { (item.name, $0) }
    })
}
