import Foundation

// MARK: - Data Models

struct ShelfSummary: Identifiable, Decodable, Hashable {
    let id: Int
    let name: String
    let slug: String
}

struct BookSummary: Identifiable, Decodable, Hashable {
    let id: Int
    let name: String
    let slug: String
}

struct ChapterSummary: Identifiable, Decodable, Hashable {
    let id: Int
    let name: String
}

struct PageResult: Decodable {
    let id: Int
    let name: String
}

// MARK: - Error

enum ShareAPIError: LocalizedError {
    case notConfigured
    case networkError(Error)
    case httpError(Int)
    case decodingError

    var errorDescription: String? {
        switch self {
        case .notConfigured:
            return NSLocalizedString("error.notConfigured", bundle: .main, comment: "")
        case .networkError(let err):
            return String(format: NSLocalizedString("error.network.format", bundle: .main, comment: ""),
                          err.localizedDescription)
        case .httpError(let code):
            return String(format: NSLocalizedString("error.http.format", bundle: .main, comment: ""),
                          code)
        case .decodingError:
            return NSLocalizedString("error.decoding", bundle: .main, comment: "")
        }
    }
}

// MARK: - Protocol (for testability)

protocol ShareAPIServiceProtocol: Sendable {
    func fetchShelves() async throws -> [ShelfSummary]
    func fetchBooks(shelfId: Int) async throws -> [BookSummary]
    func fetchChapters(bookId: Int) async throws -> [ChapterSummary]
    func createPage(bookId: Int, chapterId: Int?, title: String, markdown: String) async throws -> PageResult
}

// MARK: - Live Implementation

actor ShareExtensionAPIService: ShareAPIServiceProtocol {

    private let baseURL: String
    private let tokenId: String
    private let tokenSecret: String
    private let session: URLSession

    init(serverURL: String, tokenId: String, tokenSecret: String) {
        self.baseURL      = serverURL.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
        self.tokenId      = tokenId
        self.tokenSecret  = tokenSecret
        self.session      = URLSession(configuration: .default)
    }

    // MARK: - API calls

    func fetchShelves() async throws -> [ShelfSummary] {
        let data = try await get(path: "/api/shelves?count=500")
        return try decode(PaginatedResult<ShelfSummary>.self, from: data).data
    }

    func fetchBooks(shelfId: Int) async throws -> [BookSummary] {
        let data = try await get(path: "/api/shelves/\(shelfId)")
        return try decode(ShelfDetail.self, from: data).books ?? []
    }

    func fetchChapters(bookId: Int) async throws -> [ChapterSummary] {
        let data = try await get(path: "/api/books/\(bookId)")
        let contents = try decode(BookDetail.self, from: data).contents ?? []
        return contents.filter { $0.type == "chapter" }
                       .map { ChapterSummary(id: $0.id, name: $0.name) }
    }

    func createPage(bookId: Int, chapterId: Int?, title: String, markdown: String) async throws -> PageResult {
        var body: [String: Any] = [
            "book_id":  bookId,
            "name":     title,
            "markdown": markdown
        ]
        if let chapterId { body["chapter_id"] = chapterId }

        let bodyData = try JSONSerialization.data(withJSONObject: body)
        let url      = try makeURL(path: "/api/pages")

        var request         = URLRequest(url: url)
        request.httpMethod  = "POST"
        request.httpBody    = bodyData
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        authorize(&request)

        let (data, response) = try await session.data(for: request)
        try validate(response)
        return try decode(PageResult.self, from: data)
    }

    // MARK: - Helpers

    private func get(path: String) async throws -> Data {
        let url = try makeURL(path: path)
        var request = URLRequest(url: url)
        authorize(&request)
        do {
            let (data, response) = try await session.data(for: request)
            try validate(response)
            return data
        } catch let error as ShareAPIError {
            throw error
        } catch {
            throw ShareAPIError.networkError(error)
        }
    }

    private func makeURL(path: String) throws -> URL {
        guard let url = URL(string: baseURL + path) else {
            throw ShareAPIError.notConfigured
        }
        return url
    }

    private func authorize(_ request: inout URLRequest) {
        request.setValue("Token \(tokenId):\(tokenSecret)", forHTTPHeaderField: "Authorization")
    }

    private func validate(_ response: URLResponse) throws {
        guard let http = response as? HTTPURLResponse,
              (200..<300).contains(http.statusCode) else {
            let code = (response as? HTTPURLResponse)?.statusCode ?? 0
            throw ShareAPIError.httpError(code)
        }
    }

    private func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
        do {
            return try JSONDecoder().decode(type, from: data)
        } catch {
            throw ShareAPIError.decodingError
        }
    }
}

// MARK: - Response wrapper types (private)

private struct PaginatedResult<T: Decodable>: Decodable {
    let data: [T]
}

private struct ShelfDetail: Decodable {
    let books: [BookSummary]?
}

private struct BookContentItem: Decodable {
    let id: Int
    let name: String
    let type: String
}

private struct BookDetail: Decodable {
    let contents: [BookContentItem]?
}
