# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Stat, type: :model do
  describe 'associations' do
    it { is_expected.to belong_to(:user) }
    it { is_expected.to validate_presence_of(:year) }
    it { is_expected.to validate_presence_of(:month) }
  end

  describe 'methods' do
    let(:year) { 2021 }
    let(:user) { create(:user) }

    describe '#distance_by_day' do
      subject { stat.distance_by_day }

      let(:user) { create(:user) }
      let(:stat) { create(:stat, year:, month: 1, user:) }
      let(:expected_distance) do
        # 31 day of January
        (1..31).map { |day| [day, 0] }
      end

      context 'when there are points' do
        let!(:points) do
          create(:point, user:, lonlat: 'POINT(1 1)', timestamp: DateTime.new(year, 1, 1, 1))
          create(:point, user:, lonlat: 'POINT(2 2)', timestamp: DateTime.new(year, 1, 1, 2))
        end

        before { expected_distance[0][1] = 156_876 }

        it 'returns distance by day' do
          expect(subject).to eq(expected_distance)
        end
      end

      context 'when there are no points' do
        it 'returns distance by day' do
          expect(subject).to eq(expected_distance)
        end
      end
    end

    describe '#timespan' do
      subject { stat.send(:timespan) }

      let(:stat) { build(:stat, year:, month: 1) }
      let(:expected_timespan) { DateTime.new(year, 1).beginning_of_month..DateTime.new(year, 1).end_of_month }

      it 'returns timespan' do
        expect(subject).to eq(expected_timespan)
      end
    end

    describe '#self.year_distance' do
      subject { described_class.year_distance(year, user) }

      let(:user) { create(:user) }
      let(:expected_distance) do
        (1..12).map { |month| [Date::MONTHNAMES[month], 0] }
      end

      context 'when there are stats' do
        let!(:stats) do
          create(:stat, year:, month: 1, distance: 100, user:)
          create(:stat, year:, month: 2, distance: 200, user:)
        end

        before do
          expected_distance[0][1] = 100
          expected_distance[1][1] = 200
        end

        it 'returns year distance' do
          expect(subject).to eq(expected_distance)
        end
      end

      context 'when there are no stats' do
        it 'returns year distance' do
          expect(subject).to eq(expected_distance)
        end
      end
    end

    describe '#points' do
      subject { stat.points.to_a }

      let(:stat) { create(:stat, year:, month: 1, user:) }
      let(:base_timestamp) { DateTime.new(year, 1, 1, 5, 0, 0) }
      let!(:points) do
        [
          create(:point, user:, timestamp: base_timestamp),
          create(:point, user:, timestamp: base_timestamp + 1.hour),
          create(:point, user:, timestamp: base_timestamp + 2.hours)
        ]
      end

      it 'returns points' do
        expect(subject).to eq(points)
      end
    end

    describe '#calculate_data_bounds' do
      let(:stat) { create(:stat, year: 2024, month: 6, user:) }
      let(:user) { create(:user) }

      context 'when stat has points' do
        before do
          # Create test points within the month (June 2024)
          create(:point,
                 user:,
                 latitude: 40.6,
                 longitude: -74.1,
                 timestamp: Time.new(2024, 6, 1, 12, 0).to_i)
          create(:point,
                 user:,
                 latitude: 40.8,
                 longitude: -73.9,
                 timestamp: Time.new(2024, 6, 15, 15, 0).to_i)
          create(:point,
                 user:,
                 latitude: 40.7,
                 longitude: -74.0,
                 timestamp: Time.new(2024, 6, 30, 18, 0).to_i)

          # Points outside the month (should be ignored)
          create(:point,
                 user:,
                 latitude: 41.0,
                 longitude: -75.0,
                 timestamp: Time.new(2024, 5, 31, 23, 59).to_i) # May
          create(:point,
                 user:,
                 latitude: 39.0,
                 longitude: -72.0,
                 timestamp: Time.new(2024, 7, 1, 0, 1).to_i) # July
        end

        it 'returns correct bounding box for points within the month' do
          result = stat.calculate_data_bounds

          expect(result).to be_a(Hash)
          expect(result[:min_lat]).to eq(40.6)
          expect(result[:max_lat]).to eq(40.8)
          expect(result[:min_lng]).to eq(-74.1)
          expect(result[:max_lng]).to eq(-73.9)
          expect(result[:point_count]).to eq(3)
        end

        context 'with points from different users' do
          let(:other_user) { create(:user) }

          before do
            # Add points from a different user (should be ignored)
            create(:point,
                   user: other_user,
                   latitude: 50.0,
                   longitude: -80.0,
                   timestamp: Time.new(2024, 6, 15, 12, 0).to_i)
          end

          it 'only includes points from the stat user' do
            result = stat.calculate_data_bounds

            expect(result[:min_lat]).to eq(40.6)
            expect(result[:max_lat]).to eq(40.8)
            expect(result[:min_lng]).to eq(-74.1)
            expect(result[:max_lng]).to eq(-73.9)
            expect(result[:point_count]).to eq(3) # Still only 3 points from the stat user
          end
        end

        context 'with single point' do
          let(:single_point_user) { create(:user) }
          let(:single_point_stat) { create(:stat, year: 2024, month: 7, user: single_point_user) }

          before do
            create(:point,
                   user: single_point_user,
                   latitude: 45.5,
                   longitude: -122.65,
                   timestamp: Time.new(2024, 7, 15, 14, 30).to_i)
          end

          it 'returns bounds with same min and max values' do
            result = single_point_stat.calculate_data_bounds

            expect(result[:min_lat]).to eq(45.5)
            expect(result[:max_lat]).to eq(45.5)
            expect(result[:min_lng]).to eq(-122.65)
            expect(result[:max_lng]).to eq(-122.65)
            expect(result[:point_count]).to eq(1)
          end
        end

        context 'with edge case coordinates' do
          let(:edge_user) { create(:user) }
          let(:edge_stat) { create(:stat, year: 2024, month: 8, user: edge_user) }

          before do
            # Test with extreme coordinate values
            create(:point,
                   user: edge_user,
                   latitude: -90.0, # South Pole
                   longitude: -180.0, # Date Line West
                   timestamp: Time.new(2024, 8, 1, 0, 0).to_i)
            create(:point,
                   user: edge_user,
                   latitude: 90.0, # North Pole
                   longitude: 180.0, # Date Line East
                   timestamp: Time.new(2024, 8, 31, 23, 59).to_i)
          end

          it 'handles extreme coordinate values correctly' do
            result = edge_stat.calculate_data_bounds

            expect(result[:min_lat]).to eq(-90.0)
            expect(result[:max_lat]).to eq(90.0)
            expect(result[:min_lng]).to eq(-180.0)
            expect(result[:max_lng]).to eq(180.0)
            expect(result[:point_count]).to eq(2)
          end
        end
      end

      context 'when stat has no points' do
        let(:empty_user) { create(:user) }
        let(:empty_stat) { create(:stat, year: 2024, month: 10, user: empty_user) }

        it 'returns nil' do
          result = empty_stat.calculate_data_bounds

          expect(result).to be_nil
        end
      end

      context 'when stat has points but none within the month timeframe' do
        let(:empty_month_user) { create(:user) }
        let(:empty_month_stat) { create(:stat, year: 2024, month: 9, user: empty_month_user) }

        before do
          # Create points outside the target month
          create(:point,
                 user: empty_month_user,
                 latitude: 40.7,
                 longitude: -74.0,
                 timestamp: Time.new(2024, 8, 31, 23, 59).to_i) # August
          create(:point,
                 user: empty_month_user,
                 latitude: 40.8,
                 longitude: -73.9,
                 timestamp: Time.new(2024, 10, 1, 0, 1).to_i) # October
        end

        it 'returns nil when no points exist in the month' do
          result = empty_month_stat.calculate_data_bounds

          expect(result).to be_nil
        end
      end
    end

    describe 'sharing settings' do
      let(:user) { create(:user) }
      let(:stat) { create(:stat, year: 2024, month: 6, user: user) }

      describe '#sharing_enabled?' do
        context 'when sharing_settings is nil' do
          before { stat.update_column(:sharing_settings, nil) }

          it 'returns false' do
            expect(stat.sharing_enabled?).to be false
          end
        end

        context 'when sharing_settings is empty hash' do
          before { stat.update(sharing_settings: {}) }

          it 'returns false' do
            expect(stat.sharing_enabled?).to be false
          end
        end

        context 'when enabled is false' do
          before { stat.update(sharing_settings: { 'enabled' => false }) }

          it 'returns false' do
            expect(stat.sharing_enabled?).to be false
          end
        end

        context 'when enabled is true' do
          before { stat.update(sharing_settings: { 'enabled' => true }) }

          it 'returns true' do
            expect(stat.sharing_enabled?).to be true
          end
        end

        context 'when enabled is a string "true"' do
          before { stat.update(sharing_settings: { 'enabled' => 'true' }) }

          it 'returns false (strict boolean check)' do
            expect(stat.sharing_enabled?).to be false
          end
        end
      end

      describe '#sharing_expired?' do
        context 'when sharing_settings is nil' do
          before { stat.update_column(:sharing_settings, nil) }

          it 'returns false' do
            expect(stat.sharing_expired?).to be false
          end
        end

        context 'when expiration is blank' do
          before { stat.update(sharing_settings: { 'enabled' => true }) }

          it 'returns false' do
            expect(stat.sharing_expired?).to be false
          end
        end

        context 'when expiration is present but expires_at is blank' do
          before do
            stat.update(sharing_settings: {
              'enabled' => true,
              'expiration' => '1h'
            })
          end

          it 'returns true' do
            expect(stat.sharing_expired?).to be true
          end
        end

        context 'when expires_at is in the future' do
          before do
            stat.update(sharing_settings: {
              'enabled' => true,
              'expiration' => '1h',
              'expires_at' => 1.hour.from_now.iso8601
            })
          end

          it 'returns false' do
            expect(stat.sharing_expired?).to be false
          end
        end

        context 'when expires_at is in the past' do
          before do
            stat.update(sharing_settings: {
              'enabled' => true,
              'expiration' => '1h',
              'expires_at' => 1.hour.ago.iso8601
            })
          end

          it 'returns true' do
            expect(stat.sharing_expired?).to be true
          end
        end

        context 'when expires_at is 1 second in the future' do
          before do
            stat.update(sharing_settings: {
              'enabled' => true,
              'expiration' => '1h',
              'expires_at' => 1.second.from_now.iso8601
            })
          end

          it 'returns false (not yet expired)' do
            expect(stat.sharing_expired?).to be false
          end
        end

        context 'when expires_at is invalid date string' do
          before do
            stat.update(sharing_settings: {
              'enabled' => true,
              'expiration' => '1h',
              'expires_at' => 'invalid-date'
            })
          end

          it 'returns true (treats as expired)' do
            expect(stat.sharing_expired?).to be true
          end
        end

        context 'when expires_at is nil' do
          before do
            stat.update(sharing_settings: {
              'enabled' => true,
              'expiration' => '1h',
              'expires_at' => nil
            })
          end

          it 'returns true' do
            expect(stat.sharing_expired?).to be true
          end
        end

        context 'when expires_at is empty string' do
          before do
            stat.update(sharing_settings: {
              'enabled' => true,
              'expiration' => '1h',
              'expires_at' => ''
            })
          end

          it 'returns true' do
            expect(stat.sharing_expired?).to be true
          end
        end
      end

      describe '#public_accessible?' do
        context 'when sharing_settings is nil' do
          before { stat.update_column(:sharing_settings, nil) }

          it 'returns false' do
            expect(stat.public_accessible?).to be false
          end
        end

        context 'when sharing is not enabled' do
          before { stat.update(sharing_settings: { 'enabled' => false }) }

          it 'returns false' do
            expect(stat.public_accessible?).to be false
          end
        end

        context 'when sharing is enabled but expired' do
          before do
            stat.update(sharing_settings: {
              'enabled' => true,
              'expiration' => '1h',
              'expires_at' => 1.hour.ago.iso8601
            })
          end

          it 'returns false' do
            expect(stat.public_accessible?).to be false
          end
        end

        context 'when sharing is enabled and not expired' do
          before do
            stat.update(sharing_settings: {
              'enabled' => true,
              'expiration' => '1h',
              'expires_at' => 1.hour.from_now.iso8601
            })
          end

          it 'returns true' do
            expect(stat.public_accessible?).to be true
          end
        end

        context 'when sharing is enabled with no expiration' do
          before do
            stat.update(sharing_settings: { 'enabled' => true })
          end

          it 'returns true' do
            expect(stat.public_accessible?).to be true
          end
        end
      end
    end
  end
end
