import Foundation
import Testing
@testable import MixBoardiOS

// MARK: - ChadMusic Model Tests

@Suite("ChadMusic Models")
struct ChadMusicModelTests {
    @Test("ChadCategory decodes correctly")
    func decodeChadCategory() throws {
        let json = """
        {"item": "Rock", "count": 42}
        """.data(using: .utf8)!

        let category = try JSONDecoder().decode(ChadCategory.self, from: json)
        #expect(category.item == "Rock")
        #expect(category.count == 42)
        #expect(category.id == "Rock")
        #expect(category.name == "Rock")
    }

    @Test("ChadCategory decodes with null count")
    func decodeChadCategoryNullCount() throws {
        let json = """
        {"item": "Jazz", "count": null}
        """.data(using: .utf8)!

        let category = try JSONDecoder().decode(ChadCategory.self, from: json)
        #expect(category.item == "Jazz")
        #expect(category.count == nil)
    }

    @Test("ChadAlbum decodes with snake_case keys")
    func decodeChadAlbum() throws {
        let json = """
        {
            "id": "abc123",
            "album": "Dark Side of the Moon",
            "artist": "Pink Floyd",
            "year": 1973,
            "genre": "Progressive Rock",
            "track_count": 10,
            "cover": "/covers/abc123.jpg",
            "publisher": "Harvest",
            "country": "UK",
            "type": "Album",
            "status": "Official",
            "total_duration": 2580.0,
            "original_date": "1973-03-01",
            "mb_id": "some-uuid"
        }
        """.data(using: .utf8)!

        let album = try JSONDecoder().decode(ChadAlbum.self, from: json)
        #expect(album.id == "abc123")
        #expect(album.title == "Dark Side of the Moon")
        #expect(album.artist == "Pink Floyd")
        #expect(album.year == 1973)
        #expect(album.genre == "Progressive Rock")
        #expect(album.trackCount == 10)
        #expect(album.totalDuration == 2580.0)
    }

    @Test("ChadAlbum title defaults to Untitled when album is null")
    func chadAlbumUntitled() throws {
        let json = """
        {"id": "x", "album": null, "artist": null, "year": null, "genre": null, "track_count": null, "cover": null, "publisher": null, "country": null, "type": null, "status": null, "total_duration": null, "original_date": null, "mb_id": null}
        """.data(using: .utf8)!

        let album = try JSONDecoder().decode(ChadAlbum.self, from: json)
        #expect(album.title == "Untitled")
    }

    @Test("ChadTrack decodes with all fields")
    func decodeChadTrack() throws {
        let json = """
        {
            "id": "track1",
            "title": "Breathe",
            "artist": "Pink Floyd",
            "album_artist": "Pink Floyd",
            "album": "Dark Side of the Moon",
            "duration": 169.0,
            "no": 2,
            "url": "/music/Pink Floyd/DSOTM/02-Breathe.flac",
            "bit_rate": 1411,
            "year": 1973,
            "cover": "/covers/abc.jpg"
        }
        """.data(using: .utf8)!

        let track = try JSONDecoder().decode(ChadTrack.self, from: json)
        #expect(track.id == "track1")
        #expect(track.title == "Breathe")
        #expect(track.artist == "Pink Floyd")
        #expect(track.albumArtist == "Pink Floyd")
        #expect(track.album == "Dark Side of the Moon")
        #expect(track.duration == 169.0)
        #expect(track.trackNumber == 2)
        #expect(track.url == "/music/Pink Floyd/DSOTM/02-Breathe.flac")
        #expect(track.bitRate == 1411)
        #expect(track.year == 1973)
    }

    @Test("ChadTrack formatted duration")
    func trackFormattedDuration() throws {
        let json = """
        {"id": "t", "title": "T", "artist": null, "album_artist": null, "album": null, "duration": 185.0, "no": null, "url": "/x", "bit_rate": null, "year": null, "cover": null}
        """.data(using: .utf8)!

        let track = try JSONDecoder().decode(ChadTrack.self, from: json)
        #expect(track.formattedDuration == "3:05")
    }

    @Test("ChadTrack formatted duration with nil shows dash")
    func trackFormattedDurationNil() throws {
        let json = """
        {"id": "t", "title": "T", "artist": null, "album_artist": null, "album": null, "duration": null, "no": null, "url": "/x", "bit_rate": null, "year": null, "cover": null}
        """.data(using: .utf8)!

        let track = try JSONDecoder().decode(ChadTrack.self, from: json)
        #expect(track.formattedDuration == "—")
    }

    @Test("ChadStats decodes")
    func decodeChadStats() throws {
        let json = """
        {"tracks": 5000, "albums": 400, "artists": 200, "duration": "3d 14h 22m"}
        """.data(using: .utf8)!

        let stats = try JSONDecoder().decode(ChadStats.self, from: json)
        #expect(stats.tracks == 5000)
        #expect(stats.albums == 400)
        #expect(stats.artists == 200)
        #expect(stats.duration == "3d 14h 22m")
    }

    @Test("ChadCategoryType has correct display names")
    func categoryDisplayNames() {
        #expect(ChadCategoryType.album.displayName == "Albums")
        #expect(ChadCategoryType.artist.displayName == "Artists")
        #expect(ChadCategoryType.genre.displayName == "Genres")
        #expect(ChadCategoryType.year.displayName == "Years")
    }

    @Test("ChadCategoryType has icons")
    func categoryIcons() {
        for category in ChadCategoryType.allCases {
            #expect(!category.icon.isEmpty)
        }
    }
}

// MARK: - Track.fromCloud Tests

@Suite("Track Cloud Factory")
struct TrackFromCloudTests {
    @Test("fromCloud creates track with correct fields")
    func fromCloudBasic() {
        let chadTrack = ChadTrack(
            id: "abc123",
            title: "Test Track",
            artist: "Test Artist",
            albumArtist: nil,
            album: "Test Album",
            duration: 240.0,
            no: 3,
            url: "/music/Test/Album/03-Track.flac",
            bitRate: 1411,
            year: 2024,
            cover: nil
        )

        let track = Track.fromCloud(chadTrack)
        #expect(track.title == "Test Track")
        #expect(track.artist == "Test Artist")
        #expect(track.album == "Test Album")
        #expect(track.duration == 240.0)
        #expect(track.isCloud == true)
        #expect(track.cloudStreamPath == "/music/Test/Album/03-Track.flac")
        #expect(track.cloudTrackId == "abc123")
        #expect(track.year == 2024)
        #expect(track.filePath == "")
    }

    @Test("fromCloud handles nil optional fields")
    func fromCloudNilFields() {
        let chadTrack = ChadTrack(
            id: "x",
            title: "Minimal",
            artist: nil,
            albumArtist: nil,
            album: nil,
            duration: nil,
            no: nil,
            url: "/music/file.mp3",
            bitRate: nil,
            year: nil,
            cover: nil
        )

        let track = Track.fromCloud(chadTrack)
        #expect(track.title == "Minimal")
        #expect(track.artist == "")
        #expect(track.album == "")
        #expect(track.duration == 0)
        #expect(track.isCloud == true)
        #expect(track.year == nil)
    }

    @Test("hasLocalFile is false for cloud tracks")
    func cloudTrackHasNoLocalFile() {
        let chadTrack = ChadTrack(
            id: "x",
            title: "Cloud",
            artist: nil,
            albumArtist: nil,
            album: nil,
            duration: 100,
            no: nil,
            url: "/music/file.mp3",
            bitRate: nil,
            year: nil,
            cover: nil
        )
        let track = Track.fromCloud(chadTrack)
        #expect(track.hasLocalFile == false)
    }
}

// MARK: - ChadMusicAPIClient Tests

@Suite("ChadMusicAPIClient")
struct ChadMusicAPIClientTests {
    @Test("streamURL builds correct URL")
    @MainActor
    func streamURLBuilding() {
        let client = ChadMusicAPIClient()
        client.serverURL = "https://music.example.com"

        let url = client.streamURL(for: "/music/Artist/Album/track.flac")
        #expect(url?.absoluteString == "https://music.example.com/music/Artist/Album/track.flac")
    }

    @Test("streamURL handles trailing slash on server")
    @MainActor
    func streamURLTrailingSlash() {
        let client = ChadMusicAPIClient()
        client.serverURL = "https://music.example.com/"

        let url = client.streamURL(for: "/music/file.mp3")
        #expect(url?.absoluteString == "https://music.example.com/music/file.mp3")
    }

    @Test("streamURL handles path without leading slash")
    @MainActor
    func streamURLNoLeadingSlash() {
        let client = ChadMusicAPIClient()
        client.serverURL = "https://music.example.com"

        let url = client.streamURL(for: "music/file.mp3")
        #expect(url?.absoluteString == "https://music.example.com/music/file.mp3")
    }

    @Test("streamURL returns nil for empty server")
    @MainActor
    func streamURLEmptyServer() {
        let client = ChadMusicAPIClient()
        client.serverURL = ""

        let url = client.streamURL(for: "/music/file.mp3")
        #expect(url == nil)
    }

    @Test("isConfigured false when no URL or key")
    @MainActor
    func notConfigured() {
        let client = ChadMusicAPIClient()
        client.serverURL = ""
        #expect(client.isConfigured == false)
    }
}

// MARK: - KeychainService Tests

@Suite("KeychainService")
struct KeychainServiceTests {
    @Test("Save and load API key roundtrip")
    func saveAndLoad() throws {
        // Clean up first
        KeychainService.deleteAPIKey()

        try KeychainService.saveAPIKey("test-key-12345")
        let loaded = KeychainService.loadAPIKey()
        #expect(loaded == "test-key-12345")

        // Clean up
        KeychainService.deleteAPIKey()
    }

    @Test("Load returns nil when no key stored")
    func loadNil() {
        KeychainService.deleteAPIKey()
        let loaded = KeychainService.loadAPIKey()
        #expect(loaded == nil)
    }

    @Test("Delete removes the key")
    func deleteKey() throws {
        try KeychainService.saveAPIKey("to-delete")
        KeychainService.deleteAPIKey()
        let loaded = KeychainService.loadAPIKey()
        #expect(loaded == nil)
    }

    @Test("Saving overwrites existing key")
    func overwrite() throws {
        KeychainService.deleteAPIKey()
        try KeychainService.saveAPIKey("old-key")
        try KeychainService.saveAPIKey("new-key")
        let loaded = KeychainService.loadAPIKey()
        #expect(loaded == "new-key")

        KeychainService.deleteAPIKey()
    }
}

// MARK: - ChadMusicError Tests

@Suite("ChadMusicError")
struct ChadMusicErrorTests {
    @Test("Error descriptions are non-empty")
    func errorDescriptions() {
        let errors: [ChadMusicError] = [
            .notConfigured,
            .unauthorized,
            .forbidden,
            .notFound("test"),
            .httpError(500),
            .invalidResponse,
            .decodingFailed(NSError(domain: "test", code: 0)),
            .networkError(NSError(domain: "test", code: 0)),
        ]

        for error in errors {
            #expect(error.errorDescription != nil)
            #expect(!error.errorDescription!.isEmpty)
        }
    }
}
