require 'rails_helper'

RSpec.describe Track, type: :model do
  describe 'associations' do
    it { is_expected.to belong_to(:user) }
    it { is_expected.to have_many(:points).dependent(:nullify) }
  end

  describe 'validations' do
    subject { build(:track) }

    it { is_expected.to validate_presence_of(:start_at) }
    it { is_expected.to validate_presence_of(:end_at) }
    it { is_expected.to validate_presence_of(:original_path) }
    it { is_expected.to validate_numericality_of(:distance).is_greater_than_or_equal_to(0) }
    it { is_expected.to validate_numericality_of(:avg_speed).is_greater_than_or_equal_to(0) }
    it { is_expected.to validate_numericality_of(:duration).is_greater_than_or_equal_to(0) }
  end

  describe '.last_for_day' do
    let(:user) { create(:user) }
    let(:other_user) { create(:user) }
    let(:target_day) { Date.current }

    context 'when user has tracks on the target day' do
      let!(:early_track) do
        create(:track, user: user,
               start_at: target_day.beginning_of_day + 1.hour,
               end_at: target_day.beginning_of_day + 2.hours)
      end

      let!(:late_track) do
        create(:track, user: user,
               start_at: target_day.beginning_of_day + 3.hours,
               end_at: target_day.beginning_of_day + 4.hours)
      end

      let!(:other_user_track) do
        create(:track, user: other_user,
               start_at: target_day.beginning_of_day + 5.hours,
               end_at: target_day.beginning_of_day + 6.hours)
      end

      it 'returns the track that ends latest on that day for the user' do
        result = Track.last_for_day(user, target_day)
        expect(result).to eq(late_track)
      end

      it 'does not return tracks from other users' do
        result = Track.last_for_day(user, target_day)
        expect(result).not_to eq(other_user_track)
      end
    end

    context 'when user has tracks on different days' do
      let!(:yesterday_track) do
        create(:track, user: user,
               start_at: target_day.yesterday.beginning_of_day + 1.hour,
               end_at: target_day.yesterday.beginning_of_day + 2.hours)
      end

      let!(:tomorrow_track) do
        create(:track, user: user,
               start_at: target_day.tomorrow.beginning_of_day + 1.hour,
               end_at: target_day.tomorrow.beginning_of_day + 2.hours)
      end

      let!(:target_day_track) do
        create(:track, user: user,
               start_at: target_day.beginning_of_day + 1.hour,
               end_at: target_day.beginning_of_day + 2.hours)
      end

      it 'returns only the track from the target day' do
        result = Track.last_for_day(user, target_day)
        expect(result).to eq(target_day_track)
      end
    end

    context 'when user has no tracks on the target day' do
      let!(:yesterday_track) do
        create(:track, user: user,
               start_at: target_day.yesterday.beginning_of_day + 1.hour,
               end_at: target_day.yesterday.beginning_of_day + 2.hours)
      end

      it 'returns nil' do
        result = Track.last_for_day(user, target_day)
        expect(result).to be_nil
      end
    end

    context 'when passing a Time object instead of Date' do
      let!(:track) do
        create(:track, user: user,
               start_at: target_day.beginning_of_day + 1.hour,
               end_at: target_day.beginning_of_day + 2.hours)
      end

      it 'correctly handles Time objects' do
        result = Track.last_for_day(user, target_day.to_time)
        expect(result).to eq(track)
      end
    end

    context 'when track spans midnight' do
      let!(:spanning_track) do
        create(:track, user: user,
               start_at: target_day.beginning_of_day - 1.hour,
               end_at: target_day.beginning_of_day + 1.hour)
      end

      it 'includes tracks that end on the target day' do
        result = Track.last_for_day(user, target_day)
        expect(result).to eq(spanning_track)
      end
    end
  end

  describe 'Calculateable concern' do
    let(:user) { create(:user) }
    let(:track) { create(:track, user: user, distance: 1000, avg_speed: 25, duration: 3600) }
    let!(:points) do
      [
        create(:point, user: user, track: track, lonlat: 'POINT(13.404954 52.520008)', timestamp: 1.hour.ago.to_i),
        create(:point, user: user, track: track, lonlat: 'POINT(13.405954 52.521008)', timestamp: 30.minutes.ago.to_i),
        create(:point, user: user, track: track, lonlat: 'POINT(13.406954 52.522008)', timestamp: Time.current.to_i)
      ]
    end

    describe '#calculate_path' do
      it 'updates the original_path with calculated path' do
        original_path_before = track.original_path
        track.calculate_path

        expect(track.original_path).not_to eq(original_path_before)
        expect(track.original_path).to be_present
      end
    end

    describe '#calculate_distance' do
      it 'updates the distance based on points' do
        track.calculate_distance

        expect(track.distance).to be > 0
        expect(track.distance).to be_a(Numeric)
      end

      it 'stores distance in meters consistently' do
        allow(Point).to receive(:total_distance).and_return(1500) # 1500 meters

        track.calculate_distance

        expect(track.distance).to eq(1500) # Should be stored as meters regardless of user unit preference
      end
    end

    describe '#recalculate_distance!' do
      it 'recalculates and saves the distance' do
        original_distance = track.distance

        track.recalculate_distance!

        track.reload
        expect(track.distance).not_to eq(original_distance)
      end
    end

    describe '#recalculate_path!' do
      it 'recalculates and saves the path' do
        original_path = track.original_path

        track.recalculate_path!

        track.reload
        expect(track.original_path).not_to eq(original_path)
      end
    end

    describe '#recalculate_path_and_distance!' do
      it 'recalculates both path and distance and saves' do
        original_distance = track.distance
        original_path = track.original_path

        track.recalculate_path_and_distance!

        track.reload
        expect(track.distance).not_to eq(original_distance)
        expect(track.original_path).not_to eq(original_path)
      end
    end
  end
end
