//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import XCTest

@testable import SignalServiceKit

class LinkValidatorTest: XCTestCase {
    func testCanParseURLs() {
        XCTAssertTrue(LinkValidator.canParseURLs(in: "https://signal.org/"))
        XCTAssertFalse(LinkValidator.canParseURLs(in: "\u{202e}https://signal.org/"))
    }

    func testFirstLinkPreviewURL() {
        let testCases: [(String, String?)] = [
            // Invalid: Explicit scheme is required
            ("signal.org", nil),
            ("www.signal.org", nil),
            ("https.signal.org", nil),

            // Invalid: Scheme must be https
            ("http://www.signal.org", nil),
            ("ftp://www.signal.org", nil),

            // Valid
            ("https://signal.org", "https://signal.org"),
            ("HTTPS://signal.org", "HTTPS://signal.org"),
            ("https://www.signal.org", "https://www.signal.org"),
            ("https://www.signal.org:443/blahh.html?query=value", "https://www.signal.org:443/blahh.html?query=value"),
            ("https://test.signal.org/", "https://test.signal.org/"),

            // Invalid: Username/Password disallowed
            ("https://mlin@www.signal.org", nil),
            ("https://:pass@www.signal.org", nil),
            ("https://mlin:mypass@www.signal.org", nil),

            // Invalid: .onion TLD explicitly disallowed
            ("https://3g2upl4pq6kufc4m.onion", nil),
            ("https://3g2upl4pq6kufc4m.ONION", nil),
            ("https://3g2upl4pq6kufc4m....onion", nil),

            // Valid
            ("https://3g2upl4pq6kufc4m.onion.com", "https://3g2upl4pq6kufc4m.onion.com"),
            ("https://3g2upl4pq6kufc4m.oniony", "https://3g2upl4pq6kufc4m.oniony"),
            ("https://3g2upl4pq6kufc4m.oonion", "https://3g2upl4pq6kufc4m.oonion"),
            ("https://3g2upl4pq6kufc4m.oniony/onion", "https://3g2upl4pq6kufc4m.oniony/onion"),
            ("https://3g2upl4pq6kufc4m.oniony#onion", "https://3g2upl4pq6kufc4m.oniony#onion"),

            // Invalid: invalid tld with trailing '.'
            ("https://3g2upl4pq6kufc4m.example.", nil),
            ("https://3g2upl4pq6kufc4m.test.", nil),

            // Invalid: other invalid tld.
            ("https://3g2upl4pq6kufc4m.example", nil),
            ("https://3g2upl4pq6kufc4m.i2p", nil),
            ("https://3g2upl4pq6kufc4m.invalid", nil),
            ("https://3g2upl4pq6kufc4m.localhost", nil),

            // Invalid: example.[com,org,net]
            ("https://example.org", nil),
            ("https://example.edu", "https://example.edu"),
            ("https://example.test.org", "https://example.test.org"),
            ("https://3g2upl4pq6kufc4m.example.com.", nil),

            // Invalid, mixed-ASCII
            ("https://www.wikipediа.org", nil), // (а is cyrillic)
            ("https://www.wikipediä.org", nil),

            // This is a valid URL. Our heuristic is a little sensitive
            // If we relax our heuristic and this returns non-nil, that's okay
            ("https://中国互联网络信息中心.cn", nil),

            // NSDataDetector parsing failures
            ("https://中国互联网络信息中心。中国", nil),   // NSDataDetector does not parse a TLD of "。中国"

            // Valid, all ASCII or non-ASCII + period
            ("https://中国互联网络信息中心.中国", "https://xn--fiqa61au8b7zsevnm8ak20mc4a87e.xn--fiqs8s"),
            ("https://中国互联网络信息中心.中国/nonASCIIPath", "https://xn--fiqa61au8b7zsevnm8ak20mc4a87e.xn--fiqs8s/nonASCIIPath"),
            ("https://中国互联网络信息中心.中国?nonASCIIQuery", "https://xn--fiqa61au8b7zsevnm8ak20mc4a87e.xn--fiqs8s?nonASCIIQuery"),
            ("https://中国互联网络信息中心.中国#fragment", "https://xn--fiqa61au8b7zsevnm8ak20mc4a87e.xn--fiqs8s#fragment"),
            ("https://signal.org/你好", "https://signal.org/%E4%BD%A0%E5%A5%BD"),
            ("https://signal.org?你好", "https://signal.org?%E4%BD%A0%E5%A5%BD"),
            ("https://signal.org#你好", "https://signal.org#%E4%BD%A0%E5%A5%BD"),

            ("", nil),
            ("alice bob jim", nil),
            ("alice bob jim http://", nil),
            ("alice bob jim http://a.com", nil),

            ("https://www.youtube.com/watch?v=tP-Ipsat90c", "https://www.youtube.com/watch?v=tP-Ipsat90c"),

            ("alice bob https://www.youtube.com/watch?v=tP-Ipsat90c jim", "https://www.youtube.com/watch?v=tP-Ipsat90c"),

            // If there is more than one, take the first.
            ("alice bob https://signal.org/url_1 jim https://signal.org/url_2 carol", "https://signal.org/url_1"),

            // If there's too much text, we can't parse any URLs.
            ("https://signal.org " + String(repeating: "A", count: 4096), nil),

            // Code points that are valid outside the link, but not inside
            ("▶ https://signal.org", "https://signal.org"),
            ("https://si▶gnal.org", nil)
        ]
        for (entireMessage, expectedValue) in testCases {
            let actualValue = LinkValidator.firstLinkPreviewURL(in: .init(text: entireMessage, ranges: .empty))
            XCTAssertEqual(actualValue?.absoluteString, expectedValue, entireMessage)
        }
    }

    func testFirstLinkPreviewURLPerformance() throws {
        let entireMessage = String(repeating: "https://signal.org ", count: 1_000_000)
        measure {
            let actualValue = LinkValidator.firstLinkPreviewURL(in: .init(text: entireMessage, ranges: .empty))
            XCTAssertNil(actualValue)
        }
    }
}
