using Microsoft.Extensions.Logging;
using LocationTrackerApp.Models;
using LocationTrackerApp.Data;

namespace LocationTrackerApp.Services;

/// <summary>
/// Service for tracking user location and storing data in SQLite database
/// </summary>
public class LocationService : ILocationService, IDisposable
{
    private readonly LocationDbContext _dbContext;
    private readonly ILogger<LocationService> _logger;
    private CancellationTokenSource? _cancellationTokenSource;
    private bool _isTracking;
    private string? _currentSessionId;
    private LocationData? _lastKnownLocation;

    /// <summary>
    /// Event fired when a new location is received
    /// </summary>
    public event EventHandler<LocationData>? LocationChanged;

    /// <summary>
    /// Event fired when location tracking status changes
    /// </summary>
    public event EventHandler<bool>? TrackingStatusChanged;

    /// <summary>
    /// Gets the current location tracking status
    /// </summary>
    public bool IsTracking => _isTracking;

    /// <summary>
    /// Gets the current session ID
    /// </summary>
    public string? CurrentSessionId => _currentSessionId;

    /// <summary>
    /// Initializes a new instance of LocationService
    /// </summary>
    /// <param name="dbContext">Database context for storing location data</param>
    /// <param name="logger">Logger for recording service events</param>
    public LocationService(LocationDbContext dbContext, ILogger<LocationService> logger)
    {
        _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    /// <summary>
    /// Starts location tracking
    /// </summary>
    /// <param name="sessionId">Optional session ID for grouping location data</param>
    /// <returns>Task representing the async operation</returns>
    public async Task StartTrackingAsync(string? sessionId = null)
    {
        try
        {
            if (_isTracking)
            {
                _logger.LogWarning("Location tracking is already active");
                return;
            }

            // Check permissions first
            if (!await HasLocationPermissionsAsync())
            {
                var granted = await RequestLocationPermissionsAsync();
                if (!granted)
                {
                    _logger.LogError("Location permissions not granted");
                    throw new UnauthorizedAccessException("Location permissions are required for tracking");
                }
            }

            _currentSessionId = sessionId ?? Guid.NewGuid().ToString();
            _cancellationTokenSource = new CancellationTokenSource();

            _logger.LogInformation("Starting location tracking with session ID: {SessionId}", _currentSessionId);

            // Start location tracking
            var request = new GeolocationRequest
            {
                DesiredAccuracy = GeolocationAccuracy.Best,
                Timeout = TimeSpan.FromSeconds(10)
            };

            // Start continuous location updates
            _ = Task.Run(async () => await TrackLocationContinuouslyAsync(_cancellationTokenSource.Token));

            _isTracking = true;
            OnTrackingStatusChanged(true);

            _logger.LogInformation("Location tracking started successfully");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to start location tracking");
            throw;
        }
    }

    /// <summary>
    /// Stops location tracking
    /// </summary>
    /// <returns>Task representing the async operation</returns>
    public async Task StopTrackingAsync()
    {
        try
        {
            if (!_isTracking)
            {
                _logger.LogWarning("Location tracking is not active");
                return;
            }

            _logger.LogInformation("Stopping location tracking");

            _cancellationTokenSource?.Cancel();
            _cancellationTokenSource?.Dispose();
            _cancellationTokenSource = null;

            _isTracking = false;
            _currentSessionId = null;
            OnTrackingStatusChanged(false);

            _logger.LogInformation("Location tracking stopped successfully");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to stop location tracking");
            throw;
        }
    }

    /// <summary>
    /// Gets the current location
    /// </summary>
    /// <returns>Current location data or null if not available</returns>
    public async Task<LocationData?> GetCurrentLocationAsync()
    {
        try
        {
            if (!await HasLocationPermissionsAsync())
            {
                _logger.LogWarning("Location permissions not available");
                return null;
            }

            var request = new GeolocationRequest
            {
                DesiredAccuracy = GeolocationAccuracy.Best,
                Timeout = TimeSpan.FromSeconds(10)
            };

            var location = await Geolocation.GetLocationAsync(request);
            if (location == null)
            {
                _logger.LogWarning("Unable to get current location");
                return null;
            }

            var locationData = new LocationData
            {
                Latitude = location.Latitude,
                Longitude = location.Longitude,
                Accuracy = location.Accuracy ?? 0,
                Altitude = location.Altitude,
                Speed = location.Speed,
                Timestamp = DateTime.UtcNow,
                SessionId = _currentSessionId
            };

            _lastKnownLocation = locationData;
            return locationData;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to get current location");
            return null;
        }
    }

    /// <summary>
    /// Requests location permissions from the user
    /// </summary>
    /// <returns>True if permissions are granted, false otherwise</returns>
    public async Task<bool> RequestLocationPermissionsAsync()
    {
        try
        {
            var status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
            var granted = status == PermissionStatus.Granted;
            
            _logger.LogInformation("Location permission request result: {Status}", status);
            return granted;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to request location permissions");
            return false;
        }
    }

    /// <summary>
    /// Checks if location permissions are granted
    /// </summary>
    /// <returns>True if permissions are granted, false otherwise</returns>
    public async Task<bool> HasLocationPermissionsAsync()
    {
        try
        {
            var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
            return status == PermissionStatus.Granted;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to check location permissions");
            return false;
        }
    }

    /// <summary>
    /// Continuously tracks location updates
    /// </summary>
    /// <param name="cancellationToken">Cancellation token for stopping the tracking</param>
    private async Task TrackLocationContinuouslyAsync(CancellationToken cancellationToken)
    {
        try
        {
            while (!cancellationToken.IsCancellationRequested && _isTracking)
            {
                var locationData = await GetCurrentLocationAsync();
                if (locationData != null)
                {
                    // Save to database
                    await SaveLocationDataAsync(locationData);
                    
                    // Notify subscribers
                    OnLocationChanged(locationData);
                }

                // Wait before next update (adjust interval as needed)
                await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
            }
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("Location tracking cancelled");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error in continuous location tracking");
        }
    }

    /// <summary>
    /// Saves location data to the database
    /// </summary>
    /// <param name="locationData">Location data to save</param>
    private async Task SaveLocationDataAsync(LocationData locationData)
    {
        try
        {
            _dbContext.LocationData.Add(locationData);
            await _dbContext.SaveChangesAsync();
            
            _logger.LogDebug("Location data saved: Lat={Latitude}, Lng={Longitude}, Time={Timestamp}", 
                locationData.Latitude, locationData.Longitude, locationData.Timestamp);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to save location data to database");
        }
    }

    /// <summary>
    /// Raises the LocationChanged event
    /// </summary>
    /// <param name="locationData">The location data that changed</param>
    protected virtual void OnLocationChanged(LocationData locationData)
    {
        LocationChanged?.Invoke(this, locationData);
    }

    /// <summary>
    /// Raises the TrackingStatusChanged event
    /// </summary>
    /// <param name="isTracking">Current tracking status</param>
    protected virtual void OnTrackingStatusChanged(bool isTracking)
    {
        TrackingStatusChanged?.Invoke(this, isTracking);
    }

    /// <summary>
    /// Disposes the service and stops tracking
    /// </summary>
    public void Dispose()
    {
        try
        {
            if (_isTracking)
            {
                StopTrackingAsync().Wait();
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error disposing LocationService");
        }
        finally
        {
            _cancellationTokenSource?.Dispose();
        }
    }
}
