import Foundation

/// Synor Hosting SDK client for Swift.
///
/// Provides decentralized web hosting with domain management, DNS, deployments, SSL, and analytics.
///
/// Example:
/// ```swift
/// let hosting = SynorHosting(config: HostingConfig(apiKey: "your-api-key"))
///
/// // Check domain availability
/// let availability = try await hosting.checkAvailability(name: "mydomain.synor")
/// if availability.available {
///     // Register domain
///     let domain = try await hosting.registerDomain(name: "mydomain.synor")
///     print("Registered: \(domain.name)")
/// }
///
/// // Deploy content
/// let deployment = try await hosting.deploy(cid: "Qm...", options: DeployOptions(domain: "mydomain.synor"))
/// print("Deployed to: \(deployment.url)")
///
/// // Provision SSL
/// let cert = try await hosting.provisionSsl(domain: "mydomain.synor")
/// print("SSL status: \(cert.status)")
/// ```
public class SynorHosting {
    private let config: HostingConfig
    private let session: URLSession
    private let decoder: JSONDecoder
    private let encoder: JSONEncoder
    private var closed = false

    public init(config: HostingConfig) {
        self.config = config

        let sessionConfig = URLSessionConfiguration.default
        sessionConfig.timeoutIntervalForRequest = config.timeout
        sessionConfig.timeoutIntervalForResource = config.timeout * 2
        self.session = URLSession(configuration: sessionConfig)

        self.decoder = JSONDecoder()
        self.encoder = JSONEncoder()
    }

    // MARK: - Domain Operations

    /// Check domain availability.
    public func checkAvailability(name: String) async throws -> DomainAvailability {
        return try await get(path: "/domains/check/\(name.urlEncoded)")
    }

    /// Register a new domain.
    public func registerDomain(name: String, options: RegisterDomainOptions? = nil) async throws -> Domain {
        var body: [String: Any] = ["name": name]
        if let options = options {
            if let years = options.years { body["years"] = years }
            if let autoRenew = options.autoRenew { body["autoRenew"] = autoRenew }
        }
        return try await post(path: "/domains", body: body)
    }

    /// Get domain details.
    public func getDomain(name: String) async throws -> Domain {
        return try await get(path: "/domains/\(name.urlEncoded)")
    }

    /// List all domains.
    public func listDomains() async throws -> [Domain] {
        let response: DomainsResponse = try await get(path: "/domains")
        return response.domains ?? []
    }

    /// Update domain record.
    public func updateDomainRecord(name: String, record: DomainRecord) async throws -> Domain {
        return try await put(path: "/domains/\(name.urlEncoded)/record", body: record)
    }

    /// Resolve domain to its record.
    public func resolveDomain(name: String) async throws -> DomainRecord {
        return try await get(path: "/domains/\(name.urlEncoded)/resolve")
    }

    /// Renew a domain.
    public func renewDomain(name: String, years: Int) async throws -> Domain {
        return try await post(path: "/domains/\(name.urlEncoded)/renew", body: ["years": years])
    }

    // MARK: - DNS Operations

    /// Get DNS zone for a domain.
    public func getDnsZone(domain: String) async throws -> DnsZone {
        return try await get(path: "/dns/\(domain.urlEncoded)")
    }

    /// Set DNS records for a domain.
    public func setDnsRecords(domain: String, records: [DnsRecord]) async throws -> DnsZone {
        return try await put(path: "/dns/\(domain.urlEncoded)", body: ["records": records])
    }

    /// Add a DNS record.
    public func addDnsRecord(domain: String, record: DnsRecord) async throws -> DnsZone {
        return try await post(path: "/dns/\(domain.urlEncoded)/records", body: record)
    }

    /// Delete a DNS record.
    public func deleteDnsRecord(domain: String, recordType: String, name: String) async throws -> DnsZone {
        return try await delete(path: "/dns/\(domain.urlEncoded)/records/\(recordType)/\(name.urlEncoded)")
    }

    // MARK: - Deployment Operations

    /// Deploy content to a domain.
    public func deploy(cid: String, options: DeployOptions? = nil) async throws -> Deployment {
        var body: [String: Any] = ["cid": cid]
        if let options = options {
            if let domain = options.domain { body["domain"] = domain }
            if let subdomain = options.subdomain { body["subdomain"] = subdomain }
            if let spa = options.spa { body["spa"] = spa }
            if let cleanUrls = options.cleanUrls { body["cleanUrls"] = cleanUrls }
            if let trailingSlash = options.trailingSlash { body["trailingSlash"] = trailingSlash }
        }
        return try await post(path: "/deployments", body: body)
    }

    /// Get deployment details.
    public func getDeployment(id: String) async throws -> Deployment {
        return try await get(path: "/deployments/\(id.urlEncoded)")
    }

    /// List deployments.
    public func listDeployments(domain: String? = nil) async throws -> [Deployment] {
        var path = "/deployments"
        if let domain = domain {
            path += "?domain=\(domain.urlEncoded)"
        }
        let response: DeploymentsResponse = try await get(path: path)
        return response.deployments ?? []
    }

    /// Rollback to a previous deployment.
    public func rollback(domain: String, deploymentId: String) async throws -> Deployment {
        return try await post(
            path: "/deployments/\(deploymentId.urlEncoded)/rollback",
            body: ["domain": domain]
        )
    }

    /// Delete a deployment.
    public func deleteDeployment(id: String) async throws {
        let _: EmptyHostingResponse = try await deleteRequest(path: "/deployments/\(id.urlEncoded)")
    }

    // MARK: - SSL Operations

    /// Provision SSL certificate for a domain.
    public func provisionSsl(domain: String, options: ProvisionSslOptions? = nil) async throws -> Certificate {
        var body: [String: Any] = [:]
        if let options = options {
            if let includeWww = options.includeWww { body["includeWww"] = includeWww }
            if let autoRenew = options.autoRenew { body["autoRenew"] = autoRenew }
        }
        return try await post(path: "/ssl/\(domain.urlEncoded)", body: body.isEmpty ? nil : body)
    }

    /// Get certificate details.
    public func getCertificate(domain: String) async throws -> Certificate {
        return try await get(path: "/ssl/\(domain.urlEncoded)")
    }

    /// Renew SSL certificate.
    public func renewCertificate(domain: String) async throws -> Certificate {
        return try await post(path: "/ssl/\(domain.urlEncoded)/renew", body: nil as [String: Any]?)
    }

    /// Delete SSL certificate.
    public func deleteCertificate(domain: String) async throws {
        let _: EmptyHostingResponse = try await deleteRequest(path: "/ssl/\(domain.urlEncoded)")
    }

    // MARK: - Site Configuration

    /// Get site configuration.
    public func getSiteConfig(domain: String) async throws -> SiteConfig {
        return try await get(path: "/sites/\(domain.urlEncoded)/config")
    }

    /// Update site configuration.
    public func updateSiteConfig(domain: String, config: [String: Any]) async throws -> SiteConfig {
        return try await patch(path: "/sites/\(domain.urlEncoded)/config", body: config)
    }

    /// Purge cache.
    public func purgeCache(domain: String, paths: [String]? = nil) async throws -> Int64 {
        let body: [String: Any]? = paths.map { ["paths": $0] }
        let response: PurgeResponse = try await deleteRequest(
            path: "/sites/\(domain.urlEncoded)/cache",
            body: body
        )
        return response.purged
    }

    // MARK: - Analytics

    /// Get analytics data for a domain.
    public func getAnalytics(domain: String, options: AnalyticsOptions? = nil) async throws -> AnalyticsData {
        var params: [String] = []
        if let period = options?.period { params.append("period=\(period.urlEncoded)") }
        if let start = options?.start { params.append("start=\(start.urlEncoded)") }
        if let end = options?.end { params.append("end=\(end.urlEncoded)") }

        var path = "/sites/\(domain.urlEncoded)/analytics"
        if !params.isEmpty {
            path += "?\(params.joined(separator: "&"))"
        }
        return try await get(path: path)
    }

    // MARK: - Lifecycle

    /// Close the client.
    public func close() {
        closed = true
        session.invalidateAndCancel()
    }

    /// Check if the client is closed.
    public var isClosed: Bool { closed }

    /// Perform a health check.
    public func healthCheck() async -> Bool {
        do {
            let response: HostingHealthResponse = try await get(path: "/health")
            return response.status == "healthy"
        } catch {
            return false
        }
    }

    // MARK: - Private HTTP Methods

    private func get<T: Decodable>(path: String) async throws -> T {
        return try await executeWithRetry {
            try await self.performRequest(method: "GET", path: path, body: nil as [String: Any]?)
        }
    }

    private func post<T: Decodable>(path: String, body: [String: Any]?) async throws -> T {
        return try await executeWithRetry {
            try await self.performRequest(method: "POST", path: path, body: body)
        }
    }

    private func post<T: Decodable, R: Encodable>(path: String, body: R) async throws -> T {
        return try await executeWithRetry {
            try await self.performEncodableRequest(method: "POST", path: path, body: body)
        }
    }

    private func put<T: Decodable>(path: String, body: [String: Any]) async throws -> T {
        return try await executeWithRetry {
            try await self.performRequest(method: "PUT", path: path, body: body)
        }
    }

    private func put<T: Decodable, R: Encodable>(path: String, body: R) async throws -> T {
        return try await executeWithRetry {
            try await self.performEncodableRequest(method: "PUT", path: path, body: body)
        }
    }

    private func patch<T: Decodable>(path: String, body: [String: Any]) async throws -> T {
        return try await executeWithRetry {
            try await self.performRequest(method: "PATCH", path: path, body: body)
        }
    }

    private func delete<T: Decodable>(path: String) async throws -> T {
        return try await executeWithRetry {
            try await self.performRequest(method: "DELETE", path: path, body: nil as [String: Any]?)
        }
    }

    private func deleteRequest<T: Decodable>(path: String, body: [String: Any]? = nil) async throws -> T {
        return try await executeWithRetry {
            try await self.performRequest(method: "DELETE", path: path, body: body)
        }
    }

    private func performRequest<T: Decodable>(
        method: String,
        path: String,
        body: [String: Any]?
    ) async throws -> T {
        if closed { throw HostingError.clientClosed }

        guard let url = URL(string: "\(config.endpoint)\(path)") else {
            throw HostingError.invalidResponse
        }

        var request = URLRequest(url: url)
        request.httpMethod = method
        request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")

        if let body = body {
            do {
                request.httpBody = try JSONSerialization.data(withJSONObject: body)
            } catch {
                throw HostingError.encodingError(error)
            }
        }

        return try await executeRequest(request)
    }

    private func performEncodableRequest<T: Decodable, R: Encodable>(
        method: String,
        path: String,
        body: R
    ) async throws -> T {
        if closed { throw HostingError.clientClosed }

        guard let url = URL(string: "\(config.endpoint)\(path)") else {
            throw HostingError.invalidResponse
        }

        var request = URLRequest(url: url)
        request.httpMethod = method
        request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")

        do {
            request.httpBody = try encoder.encode(body)
        } catch {
            throw HostingError.encodingError(error)
        }

        return try await executeRequest(request)
    }

    private func executeRequest<T: Decodable>(_ request: URLRequest) async throws -> T {
        let (data, response): (Data, URLResponse)
        do {
            (data, response) = try await session.data(for: request)
        } catch {
            throw HostingError.networkError(error)
        }

        guard let httpResponse = response as? HTTPURLResponse else {
            throw HostingError.invalidResponse
        }

        if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 {
            if data.isEmpty && T.self == EmptyHostingResponse.self {
                return EmptyHostingResponse() as! T
            }
            do {
                return try decoder.decode(T.self, from: data)
            } catch {
                throw HostingError.decodingError(error)
            }
        }

        let errorInfo = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
        let message = errorInfo?["message"] as? String ?? "HTTP \(httpResponse.statusCode)"
        let code = errorInfo?["code"] as? String
        throw HostingError.httpError(statusCode: httpResponse.statusCode, message: message, code: code)
    }

    private func executeWithRetry<T>(_ operation: () async throws -> T) async throws -> T {
        var lastError: Error?

        for attempt in 0..<config.retries {
            do {
                return try await operation()
            } catch {
                lastError = error
                if config.debug {
                    print("Attempt \(attempt + 1) failed: \(error)")
                }
                if attempt < config.retries - 1 {
                    try await Task.sleep(nanoseconds: UInt64(1_000_000_000 * (1 << attempt)))
                }
            }
        }

        throw lastError ?? HostingError.invalidResponse
    }
}

// MARK: - Private Helpers

private struct EmptyHostingResponse: Decodable {}

private extension String {
    var urlEncoded: String {
        addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self
    }
}
