import Foundation
import Combine
import Security

@MainActor
class PDSService: ObservableObject {
    private var credentials: PDSCredentials?
    private var baseURL: URL?
    private var authHeader: String?
    
    // Use a shared URLSession configuration for performance
    private lazy var session: URLSession = {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 30
        config.timeoutIntervalForResource = 30
        config.waitsForConnectivity = true
        config.requestCachePolicy = .returnCacheDataElseLoad
        return URLSession(configuration: config)
    }()
    
    // Session for quick connectivity checks
    private lazy var quickSession: URLSession = {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 10
        config.timeoutIntervalForResource = 10
        config.waitsForConnectivity = false
        return URLSession(configuration: config)
    }()
    
    // Published properties for UI to observe
    @Published var isAuthenticated = false
    @Published var inviteCodes: [InviteCode] = []
    @Published var users: [PDSUser] = []
    @Published var isLoading = false
    @Published var errorMessage: String?
    
    // MARK: - Keychain Constants
    private struct KeychainConstants {
        static let service = "com.atproto.pdsmanager"
        static let usernameKey = "username"
        static let passwordKey = "password"
        static let serverURLKey = "serverURL"
        static let authHeaderKey = "authHeader"
    }
    
    init() {
        // Try to restore previous session on launch
        loadCredentialsFromKeychain()
    }
    
    // MARK: - Keychain Access
    
    private func saveToKeychain(key: String, value: String) -> Bool {
        guard let data = value.data(using: .utf8) else { return false }
        
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: KeychainConstants.service,
            kSecAttrAccount as String: key,
            kSecValueData as String: data
        ]
        
        // First delete any existing item
        SecItemDelete(query as CFDictionary)
        
        // Then add the new item
        let status = SecItemAdd(query as CFDictionary, nil)
        return status == errSecSuccess
    }
    
    private func loadFromKeychain(key: String) -> String? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: KeychainConstants.service,
            kSecAttrAccount as String: key,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]
        
        var result: AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &result)
        
        if status == errSecSuccess, let data = result as? Data, let value = String(data: data, encoding: .utf8) {
            return value
        }
        return nil
    }
    
    private func deleteFromKeychain(key: String) -> Bool {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: KeychainConstants.service,
            kSecAttrAccount as String: key
        ]
        
        let status = SecItemDelete(query as CFDictionary)
        return status == errSecSuccess || status == errSecItemNotFound
    }
    
    private func saveCredentialsToKeychain() {
        guard let credentials = credentials else { return }
        
        _ = saveToKeychain(key: KeychainConstants.usernameKey, value: credentials.username)
        _ = saveToKeychain(key: KeychainConstants.passwordKey, value: credentials.password)
        _ = saveToKeychain(key: KeychainConstants.serverURLKey, value: credentials.serverURL)
        
        if let authHeader = authHeader {
            _ = saveToKeychain(key: KeychainConstants.authHeaderKey, value: authHeader)
        }
    }
    
    private func loadCredentialsFromKeychain() {
        guard let username = loadFromKeychain(key: KeychainConstants.usernameKey),
              let password = loadFromKeychain(key: KeychainConstants.passwordKey),
              let serverURL = loadFromKeychain(key: KeychainConstants.serverURLKey),
              let authHeader = loadFromKeychain(key: KeychainConstants.authHeaderKey) else {
            return
        }
        
        // Restore the credentials
        self.credentials = PDSCredentials(serverURL: serverURL, username: username, password: password)
        self.baseURL = URL(string: serverURL)
        self.authHeader = authHeader
        self.isAuthenticated = true
        
        // Refresh data
        Task {
            await refreshData()
        }
    }
    
    private func clearCredentialsFromKeychain() {
        _ = deleteFromKeychain(key: KeychainConstants.usernameKey)
        _ = deleteFromKeychain(key: KeychainConstants.passwordKey)
        _ = deleteFromKeychain(key: KeychainConstants.serverURLKey)
        _ = deleteFromKeychain(key: KeychainConstants.authHeaderKey)
    }
    
    // MARK: - Response Structures
    
    // Invite Code structures
    struct InviteCodesResponse: Codable {
        var codes: [InviteCodeResponse]
    }
    
    struct InviteCodeResponse: Codable {
        var code: String
        var available: Int
        var disabled: Bool
        var createdAt: String
        var createdBy: String
        var uses: [CodeUse]?
    }
    
    struct CodeUse: Codable {
        var usedBy: String
        var usedAt: String
    }
    
    struct CreateCodeResponse: Codable {
        var code: String
    }
    
    // User structures
    struct RepoResponse: Codable {
        var cursor: String?
        var repos: [Repo]
    }
    
    struct Repo: Codable {
        var did: String
        var head: String
        var rev: String
        var active: Bool
    }
    
    struct AccountInfo: Codable {
        var did: String
        var handle: String
        var email: String?
        var emailConfirmedAt: String?
        var indexedAt: String
        var invitedBy: InviteInfo?
        var invites: [String]?
        var invitesDisabled: Bool?
        var createdAt: String {
            return indexedAt
        }
    }
    
    struct InviteInfo: Codable {
        var code: String
        var available: Int
        var disabled: Bool
        var forAccount: String
        var createdBy: String
        var createdAt: String
        var uses: [CodeUse]?
    }
    
    struct ProfileResponse: Codable {
        var value: ProfileValue
    }
    
    struct ProfileValue: Codable {
        var displayName: String?
        var description: String?
        var avatar: Avatar?
    }
    
    struct Avatar: Codable {
        var ref: AvatarRef
    }
    
    struct AvatarRef: Codable {
        var link: String
        
        enum CodingKeys: String, CodingKey {
            case link = "$link"
        }
    }
    
    // MARK: - Authentication
    
    private func setAuthenticated(_ value: Bool) {
        self.isAuthenticated = value
    }
    
    func login(credentials: PDSCredentials) async -> Bool {
        self.credentials = credentials
        self.isLoading = true
        defer { self.isLoading = false }
        
        // Clear any previous errors
        self.errorMessage = nil
        
        // Validate URL
        guard let url = validateServerURL(credentials.serverURL) else {
            return false
        }
        
        // Test internet connectivity
        if await !testInternetConnectivity() {
            setError("Network connectivity issue: Cannot reach the internet. Please check your network connection.")
            return false
        }
        
        self.baseURL = url
        
        // Create Basic Auth header from credentials
        guard let authHeader = createAuthHeader(username: credentials.username, password: credentials.password) else {
            setError("Invalid authentication data")
            return false
        }
        
        self.authHeader = authHeader
        
        // Test server connectivity
        guard let testURL = URL(string: "\(credentials.serverURL)/xrpc/com.atproto.server.describeServer") else {
            setError("Invalid server URL")
            return false
        }
        
        var request = URLRequest(url: testURL)
        request.httpMethod = "GET"
        
        do {
            let (data, response) = try await quickSession.data(for: request)
            
            guard let httpResponse = response as? HTTPURLResponse else {
                setError("Invalid response from server")
                return false
            }
            
            // If the server responds with 200, we consider it a success
            if httpResponse.statusCode == 200 {
                setAuthenticated(true)
                
                // Save credentials securely
                saveCredentialsToKeychain()
                
                self.errorMessage = nil
                
                // Fetch initial data
                await refreshData()
                
                return true
            } else {
                setError("Server error: \(httpResponse.statusCode)")
                return false
            }
        } catch let error as NSError {
            handleNetworkError(error)
            return false
        }
    }
    
    private func refreshData() async {
        await fetchInviteCodes()
        await fetchUsers()
    }
    
    private func validateServerURL(_ urlString: String) -> URL? {
        // Validate the URL format
        guard let components = URLComponents(string: urlString) else {
            setError("Invalid URL format: The server URL is not properly formatted")
            return nil
        }
        
        // Ensure scheme is https (we now assume all URLs use HTTPS)
        guard let scheme = components.scheme, scheme == "https" else {
            setError("Invalid URL scheme: URL must use HTTPS")
            return nil
        }
        
        // Ensure there's a host
        guard let host = components.host, !host.isEmpty else {
            setError("Invalid URL: Missing or empty host")
            return nil
        }
        
        return URL(string: urlString)
    }
    
    private func createAuthHeader(username: String, password: String) -> String? {
        let authString = "\(username):\(password)"
        guard let authData = authString.data(using: .utf8) else {
            return nil
        }
        
        let base64Auth = authData.base64EncodedString()
        return "Basic \(base64Auth)"
    }
    
    // Test basic internet connectivity by trying to reach a reliable server
    private func testInternetConnectivity() async -> Bool {
        // Use Apple's captive portal detection as a reliable server to test connectivity
        guard let connectivityURL = URL(string: "https://www.apple.com/library/test/success.html") else {
            return false
        }
        
        do {
            let (_, response) = try await quickSession.data(for: URLRequest(url: connectivityURL))
            guard let httpResponse = response as? HTTPURLResponse else {
                return false
            }
            
            return httpResponse.statusCode == 200
        } catch {
            return false
        }
    }
    
    func logout() {
        // Clear authentication state
        setAuthenticated(false)
        self.authHeader = nil
        self.credentials = nil
        self.baseURL = nil
        self.users = []
        self.inviteCodes = []
        self.errorMessage = nil
        
        // Clear saved credentials
        clearCredentialsFromKeychain()
    }
    
    // MARK: - Network Request Helper
    
    private func performRequest<T: Decodable>(
        endpoint: String,
        method: String = "GET",
        queryItems: [URLQueryItem]? = nil,
        body: [String: Any]? = nil,
        requiresAuth: Bool = true
    ) async throws -> T {
        guard let baseURL = baseURL else {
            throw URLError(.badURL)
        }
        
        // Build URL with components
        var components = URLComponents(string: "\(baseURL)/xrpc/\(endpoint)")
        if let queryItems = queryItems {
            components?.queryItems = queryItems
        }
        
        guard let url = components?.url else {
            throw URLError(.badURL)
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = method
        
        // Add authorization if required
        if requiresAuth, let authHeader = authHeader {
            request.addValue(authHeader, forHTTPHeaderField: "Authorization")
        }
        
        // Add body if provided
        if let body = body {
            request.addValue("application/json", forHTTPHeaderField: "Content-Type")
            request.httpBody = try JSONSerialization.data(withJSONObject: body)
        }
        
        let (data, response) = try await session.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse else {
            throw URLError(.badServerResponse)
        }
        
        guard 200...299 ~= httpResponse.statusCode else {
            let errorMessage = String(data: data, encoding: .utf8) ?? "Unknown error"
            throw NSError(
                domain: "PDSServiceError",
                code: httpResponse.statusCode,
                userInfo: [NSLocalizedDescriptionKey: "Server returned \(httpResponse.statusCode): \(errorMessage)"]
            )
        }
        
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        return try decoder.decode(T.self, from: data)
    }
    
    // Simplified request for endpoints that return no content
    private func performEmptyRequest(
        endpoint: String,
        method: String = "POST",
        body: [String: Any]? = nil,
        requiresAuth: Bool = true
    ) async throws {
        guard let baseURL = baseURL else {
            throw URLError(.badURL)
        }
        
        guard let url = URL(string: "\(baseURL)/xrpc/\(endpoint)") else {
            throw URLError(.badURL)
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = method
        
        // Add authorization if required
        if requiresAuth, let authHeader = authHeader {
            request.addValue(authHeader, forHTTPHeaderField: "Authorization")
        }
        
        // Add body if provided
        if let body = body {
            request.addValue("application/json", forHTTPHeaderField: "Content-Type")
            request.httpBody = try JSONSerialization.data(withJSONObject: body)
        }
        
        let (data, response) = try await session.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse else {
            throw URLError(.badServerResponse)
        }
        
        guard 200...299 ~= httpResponse.statusCode else {
            let errorMessage = String(data: data, encoding: .utf8) ?? "Unknown error"
            throw NSError(
                domain: "PDSServiceError",
                code: httpResponse.statusCode,
                userInfo: [NSLocalizedDescriptionKey: "Server returned \(httpResponse.statusCode): \(errorMessage)"]
            )
        }
    }
    
    // MARK: - Error Handling
    
    private func handleNetworkError(_ error: NSError) {
        if error.domain == NSURLErrorDomain {
            switch error.code {
            case NSURLErrorCannotFindHost:
                setError("Cannot find host: The server URL appears to be invalid or the server is unreachable")
            case NSURLErrorCannotConnectToHost:
                setError("Cannot connect to host: The server exists but isn't responding")
            case NSURLErrorNetworkConnectionLost:
                setError("Connection lost: The network connection was lost during the request")
            case NSURLErrorNotConnectedToInternet:
                setError("No internet connection: Please check your network settings")
            case NSURLErrorTimedOut:
                setError("Connection timed out: The server took too long to respond")
            default:
                setError("Network error (\(error.code)): \(error.localizedDescription)")
            }
        } else {
            setError("Connection error: \(error.localizedDescription)")
        }
    }
    
    private func setError(_ message: String) {
        self.errorMessage = message
    }
    
    // MARK: - Invite Codes
    
    func fetchInviteCodes() async {
        self.isLoading = true
        defer { self.isLoading = false }
        
        guard isAuthenticated else { return }
        
        do {
            let queryItems = [
                URLQueryItem(name: "sort", value: "recent"),
                URLQueryItem(name: "limit", value: "100"),
                URLQueryItem(name: "includeDisabled", value: "true")
            ]
            
            let codesResponse: InviteCodesResponse = try await performRequest(
                endpoint: "com.atproto.admin.getInviteCodes", 
                queryItems: queryItems
            )
            
            // Map the response to our model
            let parsedCodes = codesResponse.codes.map { codeResp -> InviteCode in
                let createdDate = ISO8601DateFormatter.shared.date(from: codeResp.createdAt) ?? Date()
                
                // Convert the uses array
                let inviteUses = codeResp.uses?.map { use -> PDSMan.CodeUse in
                    return PDSMan.CodeUse(usedBy: use.usedBy, usedAt: use.usedAt)
                }
                
                return InviteCode(
                    id: codeResp.code,
                    uses: inviteUses,
                    createdAt: createdDate,
                    disabled: codeResp.disabled
                )
            }
            
            // Update the inviteCodes property
            self.inviteCodes = parsedCodes
            self.objectWillChange.send()
            
        } catch {
            setError("Error fetching invite codes: \(error.localizedDescription)")
        }
    }
    
    func createInviteCode(maxUses: Int = 1) async -> InviteCode? {
        guard isAuthenticated else { return nil }
        
        do {
            let createBody = ["useCount": maxUses]
            
            let codeResponse: CreateCodeResponse = try await performRequest(
                endpoint: "com.atproto.server.createInviteCode",
                method: "POST",
                body: createBody
            )
            
            // Create a new InviteCode object
            let newCode = InviteCode(
                id: codeResponse.code,
                uses: [] as [PDSMan.CodeUse]?,
                createdAt: Date(),
                disabled: false
            )
            
            // Update the local list
            self.inviteCodes.append(newCode)
            self.objectWillChange.send()
            
            // Also refresh the full list to ensure we have the most up-to-date data
            await fetchInviteCodes()
            
            return newCode
        } catch {
            setError("Failed to create invite code: \(error.localizedDescription)")
            return nil
        }
    }
    
    func disableInviteCode(_ code: String) async -> Bool {
        guard isAuthenticated else { return false }
        
        do {
            // Create the request body
            let disableBody = ["codes": [code]]
            
            try await performEmptyRequest(
                endpoint: "com.atproto.admin.disableInviteCodes",
                body: disableBody
            )
            
            // Refresh the invite codes list
            await fetchInviteCodes()
            
            return true
        } catch {
            setError("Failed to disable code: \(error.localizedDescription)")
            return false
        }
    }
    
    // MARK: - Users
    
    func fetchUsers() async {
        self.isLoading = true
        defer { self.isLoading = false }
        
        guard isAuthenticated else { return }
        
        do {
            // First, get a list of all repos (users) on the server
            let reposResult: RepoResponse = try await performRequest(
                endpoint: "com.atproto.sync.listRepos"
            )
            
            // Fetch individual user profiles
            var fetchedUsers: [PDSUser] = []
            
            // Use task groups for concurrent fetching of user profiles
            await withTaskGroup(of: PDSUser?.self) { group in
                for repo in reposResult.repos {
                    group.addTask {
                        await self.fetchUserProfile(did: repo.did, isActive: repo.active)
                    }
                }
                
                // Collect results as they complete
                for await user in group {
                    if let user = user {
                        fetchedUsers.append(user)
                    }
                }
            }
            
            // Sort users by join date (newest first)
            fetchedUsers.sort { $0.joinedAt > $1.joinedAt }
            
            // Update the users property
            self.users = fetchedUsers
            self.objectWillChange.send()
            
        } catch {
            setError("Error fetching users: \(error.localizedDescription)")
        }
    }
    
    private func fetchUserProfile(did: String, isActive: Bool = true) async -> PDSUser? {
        guard isAuthenticated else { return nil }
        
        do {
            // 1. Fetch account info
            let queryItems = [URLQueryItem(name: "did", value: did)]
            
            let accountInfo: AccountInfo = try await performRequest(
                endpoint: "com.atproto.admin.getAccountInfo",
                queryItems: queryItems
            )
            
            // 2. Try to fetch profile data (optional)
            var displayName = accountInfo.handle
            var description = ""
            var avatarURL: URL? = nil
            
            // Try to fetch profile record
            do {
                let profileQueryItems = [
                    URLQueryItem(name: "collection", value: "app.bsky.actor.profile"),
                    URLQueryItem(name: "repo", value: did),
                    URLQueryItem(name: "rkey", value: "self")
                ]
                
                let profileRecord: ProfileResponse = try await performRequest(
                    endpoint: "com.atproto.repo.getRecord",
                    queryItems: profileQueryItems
                )
                
                if let name = profileRecord.value.displayName, !name.isEmpty {
                    displayName = name
                }
                
                if let desc = profileRecord.value.description, !desc.isEmpty {
                    description = desc
                }
                
                if let avatar = profileRecord.value.avatar, let baseURL = baseURL {
                    // Construct avatar URL
                    avatarURL = URL(string: "\(baseURL)/xrpc/com.atproto.sync.getBlob?did=\(did)&cid=\(avatar.ref.link)")
                }
            } catch {
                // Continue with basic info if profile fetch fails
            }
            
            let joinedDate = ISO8601DateFormatter.shared.date(from: accountInfo.indexedAt) ?? Date()
            
            return PDSUser(
                id: accountInfo.did,
                handle: accountInfo.handle,
                displayName: displayName,
                description: description,
                joinedAt: joinedDate,
                avatar: avatarURL,
                isActive: isActive
            )
            
        } catch {
            // If account info fetch fails, create a basic user
            return createBasicUser(did: did, handle: did, isActive: isActive)
        }
    }
    
    // Helper method to create a basic user with minimal info
    private func createBasicUser(did: String, handle: String, isActive: Bool) -> PDSUser {
        return PDSUser(
            id: did,
            handle: handle,
            displayName: handle,
            description: "",
            joinedAt: Date(),
            avatar: nil,
            isActive: isActive
        )
    }
    
    func editUserHandle(userId: String, newHandle: String) async -> Bool {
        guard isAuthenticated else { return false }
        
        do {
            // Create the request body
            let updateBody: [String: String] = [
                "did": userId,
                "handle": newHandle
            ]
            
            try await performEmptyRequest(
                endpoint: "com.atproto.admin.updateAccountHandle",
                body: updateBody
            )
            
            // Refresh the users list to show the updated handle
            await fetchUsers()
            return true
        } catch {
            setError("Failed to update handle: \(error.localizedDescription)")
            return false
        }
    }
    
    func resetUserPassword(userId: String) async -> Bool {
        guard isAuthenticated else { return false }
        
        do {
            let resetBody = ["did": userId]
            
            try await performEmptyRequest(
                endpoint: "com.atproto.admin.resetPassword",
                body: resetBody
            )
            
            return true
        } catch {
            setError("Failed to reset password: \(error.localizedDescription)")
            return false
        }
    }
    
    func sendResetEmail(userId: String) async -> Bool {
        guard isAuthenticated else { return false }
        
        do {
            let emailBody = [
                "recipientDid": userId,
                "subject": "Password Reset",
                "body": "Click the link to reset your password."
            ]
            
            try await performEmptyRequest(
                endpoint: "com.atproto.admin.sendEmail",
                body: emailBody
            )
            
            return true
        } catch {
            setError("Failed to send reset email: \(error.localizedDescription)")
            return false
        }
    }
    
    func deleteUser(userId: String) async -> Bool {
        guard isAuthenticated else { return false }
        
        do {
            let deleteBody = ["did": userId]
            
            try await performEmptyRequest(
                endpoint: "com.atproto.admin.deleteAccount",
                body: deleteBody
            )
            
            // Update the local list
            self.users.removeAll { $0.id == userId }
            return true
        } catch {
            setError("Failed to delete user: \(error.localizedDescription)")
            return false
        }
    }
    
    func suspendUser(userId: String, reason: String) async -> Bool {
        guard isAuthenticated else { return false }
        
        do {
            // Create the request body
            let suspendBody: [String: Any] = [
                "did": userId,
                "reason": reason
            ]
            
            try await performEmptyRequest(
                endpoint: "com.atproto.admin.disableAccountByDid",
                body: suspendBody
            )
            
            // Refresh the users list to show the updated status
            await fetchUsers()
            return true
        } catch {
            setError("Failed to suspend user: \(error.localizedDescription)")
            return false
        }
    }
    
    func reactivateUser(userId: String) async -> Bool {
        guard isAuthenticated else { return false }
        
        do {
            // Create the request body
            let reactivateBody: [String: String] = [
                "did": userId
            ]
            
            try await performEmptyRequest(
                endpoint: "com.atproto.admin.enableAccountByDid",
                body: reactivateBody
            )
            
            // Refresh the users list to show the updated status
            await fetchUsers()
            return true
        } catch {
            setError("Failed to reactivate user: \(error.localizedDescription)")
            return false
        }
    }
} 
