/*
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package com.runicgateway.app.core.net

import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull

/**
 * Parses and normalizes the base URL a user types on the first-run "Connect to
 * your shard's website" screen (PLAN.md §3). Pure, dependency-light logic so it
 * is exercised directly in JVM unit tests.
 *
 * Rules:
 *  - accept `https://host[/base]`; a bare `host[/base]` gets an implicit scheme
 *    (`https` normally, so users needn't type it);
 *  - trim surrounding whitespace;
 *  - require HTTPS unless [allowInsecureHttp] (release builds forbid HTTP; debug
 *    allows it for local dev against `127.0.0.1:3000`);
 *  - drop any query/fragment and guarantee a trailing slash on the path so the
 *    stored value composes cleanly with relative endpoint paths.
 */
object ServerUrl {

    sealed interface Result {
        data class Valid(val url: HttpUrl) : Result
        data class Invalid(val reason: Reason) : Result
    }

    enum class Reason {
        /** Empty or whitespace-only input. */
        BLANK,

        /** Not a parseable http(s) URL (bad host, illegal characters, …). */
        MALFORMED,

        /** A scheme other than http/https (e.g. ftp://, ws://). */
        UNSUPPORTED_SCHEME,

        /** Plain HTTP where the build requires HTTPS. */
        INSECURE,
    }

    fun normalize(raw: String, allowInsecureHttp: Boolean): Result {
        val trimmed = raw.trim()
        if (trimmed.isEmpty()) return Result.Invalid(Reason.BLANK)

        // Give a scheme-less entry an implicit, secure default so users can type
        // just "shard.example.com". An explicit but unsupported scheme is rejected.
        val hasScheme = SCHEME_RE.containsMatchIn(trimmed)
        val candidate = if (hasScheme) trimmed else "https://$trimmed"

        val lowerScheme = candidate.substringBefore("://", "").lowercase()
        if (hasScheme && lowerScheme != "http" && lowerScheme != "https") {
            return Result.Invalid(Reason.UNSUPPORTED_SCHEME)
        }

        val parsed = candidate.toHttpUrlOrNull() ?: return Result.Invalid(Reason.MALFORMED)
        if (parsed.host.isBlank()) return Result.Invalid(Reason.MALFORMED)

        if (parsed.scheme == "http" && !allowInsecureHttp) {
            return Result.Invalid(Reason.INSECURE)
        }

        // Rebuild without query/fragment and force a trailing slash so
        // HttpUrl.resolve()/addPathSegments compose predictably later.
        val path = parsed.encodedPath.trimEnd('/')
        val normalized = parsed.newBuilder()
            .encodedPath(if (path.isEmpty()) "/" else "$path/")
            .query(null)
            .fragment(null)
            .build()

        return Result.Valid(normalized)
    }

    private val SCHEME_RE = Regex("^[a-zA-Z][a-zA-Z0-9+.-]*://")
}
