import Foundation

enum GoogleMeetClientError: Error {
    case invalidResponse
    case httpStatus(Int, String)
    case decodeFailed(String)
}

/// Thin Meet REST API wrapper.
/// Note: Meet REST API is best used for conferences/participants/artifacts, while scheduling comes from Calendar.
final class GoogleMeetClient {
    private let session: URLSession

    init(session: URLSession = .shared) {
        self.session = session
    }

    func getSpace(accessToken: String, spaceNameOrMeetingCode: String) async throws -> Space {
        let encoded = spaceNameOrMeetingCode.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? spaceNameOrMeetingCode
        let url = URL(string: "https://meet.googleapis.com/v2/\(encoded)")!
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")

        let (data, response) = try await session.data(for: request)
        guard let http = response as? HTTPURLResponse else { throw GoogleMeetClientError.invalidResponse }
        guard (200..<300).contains(http.statusCode) else {
            let body = String(data: data, encoding: .utf8) ?? "<no body>"
            throw GoogleMeetClientError.httpStatus(http.statusCode, body)
        }

        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        do {
            return try decoder.decode(Space.self, from: data)
        } catch {
            let raw = String(data: data, encoding: .utf8) ?? "<unreadable body>"
            throw GoogleMeetClientError.decodeFailed(raw)
        }
    }

    /// Lists conference records filtered by meeting space resource name.
    func listConferenceRecords(accessToken: String, spaceResourceName: String, pageSize: Int = 10) async throws -> [ConferenceRecord] {
        var components = URLComponents(string: "https://meet.googleapis.com/v2/conferenceRecords")!
        components.queryItems = [
            URLQueryItem(name: "pageSize", value: String(pageSize)),
            URLQueryItem(name: "filter", value: "space.name = \"\(spaceResourceName)\"")
        ]

        var request = URLRequest(url: components.url!)
        request.httpMethod = "GET"
        request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")

        let (data, response) = try await session.data(for: request)
        guard let http = response as? HTTPURLResponse else { throw GoogleMeetClientError.invalidResponse }
        guard (200..<300).contains(http.statusCode) else {
            let body = String(data: data, encoding: .utf8) ?? "<no body>"
            throw GoogleMeetClientError.httpStatus(http.statusCode, body)
        }

        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        let decoded: ListConferenceRecordsResponse
        do {
            decoded = try decoder.decode(ListConferenceRecordsResponse.self, from: data)
        } catch {
            let raw = String(data: data, encoding: .utf8) ?? "<unreadable body>"
            throw GoogleMeetClientError.decodeFailed(raw)
        }
        return decoded.conferenceRecords ?? []
    }

    func listTranscripts(accessToken: String, conferenceRecordName: String, pageSize: Int = 20) async throws -> [Transcript] {
        var transcripts: [Transcript] = []
        var nextPageToken: String?

        repeat {
            var components = URLComponents(string: "https://meet.googleapis.com/v2/\(conferenceRecordName)/transcripts")!
            components.queryItems = [
                URLQueryItem(name: "pageSize", value: String(pageSize)),
                URLQueryItem(name: "pageToken", value: nextPageToken ?? "")
            ].filter { $0.value?.isEmpty == false }

            var request = URLRequest(url: components.url!)
            request.httpMethod = "GET"
            request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")

            let (data, response) = try await session.data(for: request)
            guard let http = response as? HTTPURLResponse else { throw GoogleMeetClientError.invalidResponse }
            guard (200..<300).contains(http.statusCode) else {
                let body = String(data: data, encoding: .utf8) ?? "<no body>"
                throw GoogleMeetClientError.httpStatus(http.statusCode, body)
            }

            let decoder = JSONDecoder()
            decoder.dateDecodingStrategy = .iso8601
            let decoded: ListTranscriptsResponse
            do {
                decoded = try decoder.decode(ListTranscriptsResponse.self, from: data)
            } catch {
                let raw = String(data: data, encoding: .utf8) ?? "<unreadable body>"
                throw GoogleMeetClientError.decodeFailed(raw)
            }
            transcripts.append(contentsOf: decoded.transcripts ?? [])
            nextPageToken = decoded.nextPageToken
        } while nextPageToken != nil

        return transcripts
    }

    func listTranscriptEntries(
        accessToken: String,
        transcriptName: String,
        pageSize: Int = 200,
        maxEntries: Int = 10_000
    ) async throws -> [TranscriptEntry] {
        var entries: [TranscriptEntry] = []
        var nextPageToken: String?

        repeat {
            var components = URLComponents(string: "https://meet.googleapis.com/v2/\(transcriptName)/entries")!
            components.queryItems = [
                URLQueryItem(name: "pageSize", value: String(pageSize)),
                URLQueryItem(name: "pageToken", value: nextPageToken ?? "")
            ].filter { $0.value?.isEmpty == false }

            var request = URLRequest(url: components.url!)
            request.httpMethod = "GET"
            request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")

            let (data, response) = try await session.data(for: request)
            guard let http = response as? HTTPURLResponse else { throw GoogleMeetClientError.invalidResponse }
            guard (200..<300).contains(http.statusCode) else {
                let body = String(data: data, encoding: .utf8) ?? "<no body>"
                throw GoogleMeetClientError.httpStatus(http.statusCode, body)
            }

            let decoder = JSONDecoder()
            decoder.dateDecodingStrategy = .iso8601
            let decoded: ListTranscriptEntriesResponse
            do {
                decoded = try decoder.decode(ListTranscriptEntriesResponse.self, from: data)
            } catch {
                let raw = String(data: data, encoding: .utf8) ?? "<unreadable body>"
                throw GoogleMeetClientError.decodeFailed(raw)
            }
            entries.append(contentsOf: decoded.transcriptEntries ?? [])
            nextPageToken = decoded.nextPageToken

            if entries.count >= maxEntries {
                break
            }
        } while nextPageToken != nil

        return entries
    }
}

// MARK: - Minimal models (v2)

struct ConferenceRecord: Decodable, Equatable {
    let name: String?
    let startTime: Date?
    let endTime: Date?
}

struct Space: Decodable, Equatable {
    let name: String?
    let meetingUri: String?
    let meetingCode: String?
}

struct Transcript: Decodable, Equatable {
    let name: String?
    let state: String?
    let startTime: Date?
    let endTime: Date?
}

struct TranscriptEntry: Decodable, Equatable {
    let name: String?
    let participant: String?
    let text: String?
    let languageCode: String?
    let startTime: Date?
    let endTime: Date?
}

private struct ListConferenceRecordsResponse: Decodable {
    let conferenceRecords: [ConferenceRecord]?
    let nextPageToken: String?
}

private struct ListTranscriptsResponse: Decodable {
    let transcripts: [Transcript]?
    let nextPageToken: String?
}

private struct ListTranscriptEntriesResponse: Decodable {
    let transcriptEntries: [TranscriptEntry]?
    let nextPageToken: String?
}

extension GoogleMeetClientError: LocalizedError {
    var errorDescription: String? {
        switch self {
        case .invalidResponse:
            return "Google Meet returned an invalid response."
        case let .httpStatus(status, body):
            return "Google Meet API error (\(status)): \(body)"
        case let .decodeFailed(raw):
            return "Failed to parse Google Meet response: \(raw)"
        }
    }
}
