import Foundation

/// Typed errors surfaced by `APIClient`. HTTP status codes from the MCMS docs
/// are mapped to specific cases so the UI can react (e.g. re-auth on `.unauthorized`).
enum APIError: Error, Equatable {
    /// No usable base URL / malformed endpoint.
    case invalidURL
    /// The request never produced an HTTP response (offline, host unreachable, VPN off).
    case transport(message: String)
    /// TLS trust evaluation failed for the server.
    case serverTrust
    /// Could not encode the request body.
    case encoding(message: String)
    /// Could not decode the response body into the expected type.
    case decoding(message: String)

    /// 400 — request/validation error. `details` carries the server payload.
    case badRequest(details: JSONValue?)
    /// 401 — not authenticated (failed login or expired session). Trigger re-login.
    case unauthorized
    /// 403 — authenticated but not permitted, OR a missing/invalid CSRF token.
    case forbidden(details: JSONValue?)
    /// 404 — resource (URL, file, or document) not found.
    case notFound
    /// 409 — ID already in use (uploads / creates).
    case conflict(details: JSONValue?)
    /// 415 — unsupported Content-Type.
    case unsupportedMediaType
    /// 500 — generic server error.
    case server(details: JSONValue?)

    /// The API returned HTTP 2xx but `status == "fail"`.
    case apiFailure(details: JSONValue?)
    /// Any other unexpected status code.
    case unexpectedStatus(code: Int, details: JSONValue?)

    /// Maps an HTTP status code (with optional decoded `details`) to a case.
    static func from(statusCode: Int, details: JSONValue?) -> APIError {
        switch statusCode {
        case 400: return .badRequest(details: details)
        case 401: return .unauthorized
        case 403: return .forbidden(details: details)
        case 404: return .notFound
        case 409: return .conflict(details: details)
        case 415: return .unsupportedMediaType
        case 500: return .server(details: details)
        default:  return .unexpectedStatus(code: statusCode, details: details)
        }
    }

    /// True when the right response is to send the user back to the login screen.
    /// Only 401 (expired/invalid session) qualifies — a 403 is usually a
    /// permission or CSRF issue where re-login won't help, so it must NOT bounce
    /// the user out mid-action.
    var requiresReauthentication: Bool {
        switch self {
        case .unauthorized: return true
        default: return false
        }
    }
}

extension APIError: LocalizedError {
    var errorDescription: String? {
        switch self {
        case .invalidURL: return "The request URL was invalid."
        case .transport(let m): return "Network error: \(m)"
        case .serverTrust: return "The server's TLS certificate could not be trusted."
        case .encoding(let m): return "Failed to encode request: \(m)"
        case .decoding(let m): return "Failed to read server response: \(m)"
        case .badRequest(let d): return "Request rejected (400). \(Self.detailText(d))"
        case .unauthorized: return "Not authenticated (401). Please sign in again."
        case .forbidden(let d): return "Forbidden (403) — your account may not have permission for this action. \(Self.detailText(d))"
        case .notFound: return "Resource not found (404)."
        case .conflict(let d): return "Conflict (409) — ID already in use. \(Self.detailText(d))"
        case .unsupportedMediaType: return "Unsupported media type (415)."
        case .server(let d): return "Server error (500). \(Self.detailText(d))"
        case .apiFailure(let d): return "Request failed. \(Self.detailText(d))"
        case .unexpectedStatus(let c, let d): return "Unexpected response (\(c)). \(Self.detailText(d))"
        }
    }

    private static func detailText(_ details: JSONValue?) -> String {
        guard let details else { return "" }
        return details.compactDescription
    }
}
