//
//  PhotoMetadataExtractor.swift
//  SportsTime
//
//  Service for extracting EXIF metadata (GPS, date) from photos.
//

import Foundation
import Photos
import CoreLocation
import ImageIO
import UIKit

// MARK: - Photo Metadata

struct PhotoMetadata: Sendable {
    let captureDate: Date?
    let coordinates: CLLocationCoordinate2D?
    let hasValidLocation: Bool
    let hasValidDate: Bool

    nonisolated init(captureDate: Date?, coordinates: CLLocationCoordinate2D?) {
        self.captureDate = captureDate
        self.coordinates = coordinates
        self.hasValidLocation = coordinates != nil
        self.hasValidDate = captureDate != nil
    }

    nonisolated static var empty: PhotoMetadata {
        PhotoMetadata(captureDate: nil, coordinates: nil)
    }
}

// MARK: - Photo Metadata Extractor

actor PhotoMetadataExtractor {
    static let shared = PhotoMetadataExtractor()

    private init() {}

    // MARK: - PHAsset Extraction (Preferred)

    /// Extract metadata from PHAsset (preferred method)
    /// Uses PHAsset's location and creationDate properties
    func extractMetadata(from asset: PHAsset) async -> PhotoMetadata {
        print("📸 [PhotoMetadata] ════════════════════════════════════════")
        print("📸 [PhotoMetadata] Extracting from PHAsset: \(asset.localIdentifier)")
        print("📸 [PhotoMetadata] Asset mediaType: \(asset.mediaType.rawValue) (1=image, 2=video)")
        print("📸 [PhotoMetadata] Asset creationDate: \(asset.creationDate?.description ?? "nil")")
        print("📸 [PhotoMetadata] Asset modificationDate: \(asset.modificationDate?.description ?? "nil")")
        print("📸 [PhotoMetadata] Asset location: \(asset.location?.description ?? "nil")")

        // PHAsset provides location and date directly
        let coordinates: CLLocationCoordinate2D?
        if let location = asset.location {
            coordinates = location.coordinate
            print("📸 [PhotoMetadata] ✅ Location found: \(coordinates!.latitude), \(coordinates!.longitude)")
        } else {
            coordinates = nil
            print("📸 [PhotoMetadata] ⚠️ No location in PHAsset, trying ImageIO fallback...")

            // Try ImageIO extraction as fallback
            if let imageData = await loadImageData(from: asset) {
                print("📸 [PhotoMetadata] Loaded image data: \(imageData.count) bytes")
                let fallbackMetadata = extractMetadata(from: imageData)
                if fallbackMetadata.hasValidLocation || fallbackMetadata.hasValidDate {
                    print("📸 [PhotoMetadata] ✅ ImageIO fallback found data - date: \(fallbackMetadata.captureDate?.description ?? "nil"), coords: \(fallbackMetadata.coordinates?.latitude ?? 0), \(fallbackMetadata.coordinates?.longitude ?? 0)")
                    return fallbackMetadata
                } else {
                    print("📸 [PhotoMetadata] ⚠️ ImageIO fallback found no metadata either")
                }
            }
        }

        let result = PhotoMetadata(
            captureDate: asset.creationDate,
            coordinates: coordinates
        )
        print("📸 [PhotoMetadata] Final result - hasDate: \(result.hasValidDate), hasLocation: \(result.hasValidLocation)")
        print("📸 [PhotoMetadata] ════════════════════════════════════════")
        return result
    }

    /// Extract metadata from multiple PHAssets
    func extractMetadata(from assets: [PHAsset]) async -> [PhotoMetadata] {
        var results: [PhotoMetadata] = []
        for asset in assets {
            let metadata = await extractMetadata(from: asset)
            results.append(metadata)
        }
        return results
    }

    // MARK: - Image Data Extraction (Fallback)

    /// Extract metadata from raw image data using ImageIO
    /// Useful when PHAsset is not available
    func extractMetadata(from imageData: Data) -> PhotoMetadata {
        print("📸 [ImageIO] Extracting from image data (\(imageData.count) bytes)")

        guard let source = CGImageSourceCreateWithData(imageData as CFData, nil) else {
            print("📸 [ImageIO] ❌ Failed to create CGImageSource")
            return .empty
        }

        guard let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any] else {
            print("📸 [ImageIO] ❌ Failed to copy properties from source")
            return .empty
        }

        // Debug: Print all available property dictionaries
        print("📸 [ImageIO] Available property keys: \(properties.keys.map { $0 as String })")

        if let exif = properties[kCGImagePropertyExifDictionary] as? [CFString: Any] {
            print("📸 [ImageIO] EXIF keys: \(exif.keys.map { $0 as String })")
            if let dateOriginal = exif[kCGImagePropertyExifDateTimeOriginal] {
                print("📸 [ImageIO] EXIF DateTimeOriginal: \(dateOriginal)")
            }
            if let dateDigitized = exif[kCGImagePropertyExifDateTimeDigitized] {
                print("📸 [ImageIO] EXIF DateTimeDigitized: \(dateDigitized)")
            }
        } else {
            print("📸 [ImageIO] ⚠️ No EXIF dictionary found")
        }

        if let gps = properties[kCGImagePropertyGPSDictionary] as? [CFString: Any] {
            print("📸 [ImageIO] GPS keys: \(gps.keys.map { $0 as String })")
            print("📸 [ImageIO] GPS Latitude: \(gps[kCGImagePropertyGPSLatitude] ?? "nil")")
            print("📸 [ImageIO] GPS LatitudeRef: \(gps[kCGImagePropertyGPSLatitudeRef] ?? "nil")")
            print("📸 [ImageIO] GPS Longitude: \(gps[kCGImagePropertyGPSLongitude] ?? "nil")")
            print("📸 [ImageIO] GPS LongitudeRef: \(gps[kCGImagePropertyGPSLongitudeRef] ?? "nil")")
        } else {
            print("📸 [ImageIO] ⚠️ No GPS dictionary found")
        }

        if let tiff = properties[kCGImagePropertyTIFFDictionary] as? [CFString: Any] {
            print("📸 [ImageIO] TIFF keys: \(tiff.keys.map { $0 as String })")
            if let dateTime = tiff[kCGImagePropertyTIFFDateTime] {
                print("📸 [ImageIO] TIFF DateTime: \(dateTime)")
            }
        } else {
            print("📸 [ImageIO] ⚠️ No TIFF dictionary found")
        }

        let captureDate = extractDate(from: properties)
        let coordinates = extractCoordinates(from: properties)

        print("📸 [ImageIO] Extracted date: \(captureDate?.description ?? "nil")")
        print("📸 [ImageIO] Extracted coordinates: \(coordinates?.latitude ?? 0), \(coordinates?.longitude ?? 0)")

        return PhotoMetadata(
            captureDate: captureDate,
            coordinates: coordinates
        )
    }

    // MARK: - Private Helpers

    private func extractDate(from properties: [CFString: Any]) -> Date? {
        // Try EXIF DateTimeOriginal first
        if let exif = properties[kCGImagePropertyExifDictionary] as? [CFString: Any],
           let dateString = exif[kCGImagePropertyExifDateTimeOriginal] as? String {
            return parseExifDate(dateString)
        }

        // Try TIFF DateTime
        if let tiff = properties[kCGImagePropertyTIFFDictionary] as? [CFString: Any],
           let dateString = tiff[kCGImagePropertyTIFFDateTime] as? String {
            return parseExifDate(dateString)
        }

        return nil
    }

    private func extractCoordinates(from properties: [CFString: Any]) -> CLLocationCoordinate2D? {
        guard let gps = properties[kCGImagePropertyGPSDictionary] as? [CFString: Any],
              let latitude = gps[kCGImagePropertyGPSLatitude] as? Double,
              let longitude = gps[kCGImagePropertyGPSLongitude] as? Double else {
            return nil
        }

        // Handle N/S and E/W references
        var lat = latitude
        var lon = longitude

        if let latRef = gps[kCGImagePropertyGPSLatitudeRef] as? String, latRef == "S" {
            lat = -lat
        }

        if let lonRef = gps[kCGImagePropertyGPSLongitudeRef] as? String, lonRef == "W" {
            lon = -lon
        }

        // Validate coordinates
        guard lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180 else {
            return nil
        }

        return CLLocationCoordinate2D(latitude: lat, longitude: lon)
    }

    private func parseExifDate(_ dateString: String) -> Date? {
        // EXIF date format: "2024:06:15 14:30:00"
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy:MM:dd HH:mm:ss"
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter.date(from: dateString)
    }
}

// MARK: - Photo Library Access

extension PhotoMetadataExtractor {
    /// Request photo library access
    @MainActor
    func requestPhotoLibraryAccess() async -> PHAuthorizationStatus {
        let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)

        switch status {
        case .notDetermined:
            return await PHPhotoLibrary.requestAuthorization(for: .readWrite)
        default:
            return status
        }
    }

    /// Check if photo library access is available
    var hasPhotoLibraryAccess: Bool {
        let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
        return status == .authorized || status == .limited
    }
}

// MARK: - Asset Image Loading

extension PhotoMetadataExtractor {
    /// Load thumbnail image from PHAsset
    func loadThumbnail(from asset: PHAsset, targetSize: CGSize = CGSize(width: 200, height: 200)) async -> UIImage? {
        await withCheckedContinuation { continuation in
            let options = PHImageRequestOptions()
            options.deliveryMode = .fastFormat
            options.resizeMode = .fast
            options.isSynchronous = false

            PHImageManager.default().requestImage(
                for: asset,
                targetSize: targetSize,
                contentMode: .aspectFill,
                options: options
            ) { image, _ in
                continuation.resume(returning: image)
            }
        }
    }

    /// Load full-size image data from PHAsset
    func loadImageData(from asset: PHAsset) async -> Data? {
        await withCheckedContinuation { continuation in
            let options = PHImageRequestOptions()
            options.deliveryMode = .highQualityFormat
            options.isSynchronous = false

            PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { data, _, _, _ in
                continuation.resume(returning: data)
            }
        }
    }
}
