import { describe, it, expect, beforeEach } from 'vitest';
import { KGMidiTrack, type InstrumentType } from './KGMidiTrack';
import { KGTrack, TrackType } from './KGTrack';
import { KGMidiRegion } from '../region/KGMidiRegion';
import { createMockMidiRegion } from '../../test/utils/mock-data';

describe('KGMidiTrack', () => {
  let track: KGMidiTrack;

  beforeEach(() => {
    track = new KGMidiTrack();
  });

  describe('constructor', () => {
    it('should create track with default values', () => {
      const defaultTrack = new KGMidiTrack();

      expect(defaultTrack.getName()).toBe('Untitled MIDI Track');
      expect(defaultTrack.getId()).toBe(0);
      expect(defaultTrack.getType()).toBe(TrackType.MIDI);
      expect(defaultTrack.getInstrument()).toBe('acoustic_grand_piano');
      expect(defaultTrack.getVolume()).toBe(0); // DEFAULT_TRACK_VOLUME (0 dB)
      expect(defaultTrack.getRegions()).toEqual([]);
    });

    it('should create track with custom parameters', () => {
      const customTrack = new KGMidiTrack('My Piano Track', 5, 'electric_piano_1', -4);

      expect(customTrack.getName()).toBe('My Piano Track');
      expect(customTrack.getId()).toBe(5);
      expect(customTrack.getType()).toBe(TrackType.MIDI);
      expect(customTrack.getInstrument()).toBe('electric_piano_1');
      expect(customTrack.getVolume()).toBe(-4);
    });

    it('should set correct type identifier', () => {
      expect(track.getCurrentType()).toBe('KGMidiTrack');
    });

    it('should inherit from KGTrack', () => {
      expect(track).toBeInstanceOf(KGMidiTrack);
      expect(track).toBeInstanceOf(KGTrack);
    });
  });

  describe('instrument management', () => {
    describe('getInstrument', () => {
      it('should return default instrument when not set', () => {
        expect(track.getInstrument()).toBe('acoustic_grand_piano');
      });

      it('should return current instrument', () => {
        const customTrack = new KGMidiTrack('Test', 0, 'electric_guitar_clean');
        expect(customTrack.getInstrument()).toBe('electric_guitar_clean');
      });

      it('should provide backward compatibility for undefined instrument', () => {
        // This tests the backward compatibility mentioned in the code
        expect(track.getInstrument()).toBe('acoustic_grand_piano');
      });
    });

    describe('setInstrument', () => {
      it('should update instrument', () => {
        track.setInstrument('violin');
        expect(track.getInstrument()).toBe('violin');
      });

      it('should handle different instrument types', () => {
        const instruments: InstrumentType[] = [
          'acoustic_grand_piano',
          'electric_piano_1',
          'electric_guitar_clean',
          'acoustic_bass',
          'violin',
          'trumpet',
          'flute'
        ];

        instruments.forEach(instrument => {
          track.setInstrument(instrument);
          expect(track.getInstrument()).toBe(instrument);
        });
      });

      it('should handle rapid instrument changes', () => {
        track.setInstrument('piano');
        track.setInstrument('guitar');
        track.setInstrument('violin');
        
        expect(track.getInstrument()).toBe('violin');
      });
    });
  });

  describe('region management', () => {
    let region1: KGMidiRegion;
    let region2: KGMidiRegion;
    let region3: KGMidiRegion;

    beforeEach(() => {
      region1 = createMockMidiRegion({ 
        id: 'region-1', 
        trackId: track.getId().toString(),
        name: 'Region 1',
        startFromBeat: 0,
        length: 4
      });
      region2 = createMockMidiRegion({ 
        id: 'region-2', 
        trackId: track.getId().toString(),
        name: 'Region 2',
        startFromBeat: 4,
        length: 4
      });
      region3 = createMockMidiRegion({ 
        id: 'region-3', 
        trackId: track.getId().toString(),
        name: 'Region 3',
        startFromBeat: 8,
        length: 4
      });
    });

    describe('setRegions', () => {
      it('should set regions array', () => {
        const regions = [region1, region2];
        track.setRegions(regions);

        expect(track.getRegions()).toHaveLength(2);
        expect(track.getRegions()).toEqual(regions);
      });

      it('should replace existing regions', () => {
        track.setRegions([region1]);
        expect(track.getRegions()).toHaveLength(1);

        track.setRegions([region2, region3]);
        expect(track.getRegions()).toHaveLength(2);
        expect(track.getRegions()).toContain(region2);
        expect(track.getRegions()).toContain(region3);
        expect(track.getRegions()).not.toContain(region1);
      });

      it('should accept empty array', () => {
        track.setRegions([region1, region2]);
        track.setRegions([]);

        expect(track.getRegions()).toHaveLength(0);
      });

      it('should enforce KGMidiRegion type', () => {
        const regions: KGMidiRegion[] = [region1, region2];
        track.setRegions(regions);

        const retrievedRegions = track.getRegions();
        retrievedRegions.forEach(region => {
          expect(region).toBeInstanceOf(KGMidiRegion);
        });
      });
    });

    describe('inherited region methods', () => {
      beforeEach(() => {
        track.setRegions([region1, region2]);
      });

      it('should inherit addRegion method', () => {
        track.addRegion(region3);

        const regions = track.getRegions();
        expect(regions).toHaveLength(3);
        expect(regions).toContain(region3);
      });

      it('should inherit removeRegion method', () => {
        track.removeRegion('region-1');

        const regions = track.getRegions();
        expect(regions).toHaveLength(1);
        expect(regions).not.toContain(region1);
        expect(regions).toContain(region2);
      });

      it('should inherit getRegions method', () => {
        const regions = track.getRegions();
        expect(regions).toHaveLength(2);
        expect(regions).toContain(region1);
        expect(regions).toContain(region2);
      });
    });
  });

  describe('inheritance from KGTrack', () => {
    it('should inherit all base track properties', () => {
      const customTrack = new KGMidiTrack('Test Track', 42, 'violin', -1);

      expect(customTrack.getName()).toBe('Test Track');
      expect(customTrack.getId()).toBe(42);
      expect(customTrack.getType()).toBe(TrackType.MIDI);
      expect(customTrack.getVolume()).toBe(-1);
    });

    it('should inherit base track setters', () => {
      track.setName('Updated Track');
      expect(track.getName()).toBe('Updated Track');

      track.setVolume(-6);
      expect(track.getVolume()).toBe(-6);

      track.setTrackIndex(3);
      expect(track.getTrackIndex()).toBe(3);
    });

    it('should inherit volume controls', () => {
      expect(track.getVolume()).toBe(0); // Default volume (0 dB)

      track.setVolume(-6);
      expect(track.getVolume()).toBe(-6);

      track.setVolume(0);
      expect(track.getVolume()).toBe(0);
    });
  });

  describe('type identification', () => {
    it('should return correct current type', () => {
      expect(track.getCurrentType()).toBe('KGMidiTrack');
    });

    it('should return correct root type', () => {
      expect(track.getRootType()).toBe('KGTrack');
    });

    it('should have MIDI track type', () => {
      expect(track.getType()).toBe(TrackType.MIDI);
    });
  });

  describe('instrument type validation', () => {
    it('should handle all valid General MIDI instruments', () => {
      // Test a selection of valid GM instruments
      const validInstruments: InstrumentType[] = [
        'acoustic_grand_piano',
        'electric_piano_1',
        'electric_piano_2',
        'electric_guitar_clean',
        'electric_guitar_muted',
        'acoustic_bass',
        'electric_bass_finger',
        'violin',
        'viola',
        'cello',
        'contrabass',
        'trumpet',
        'trombone',
        'french_horn',
        'flute',
        'clarinet',
        'soprano_sax',
        'alto_sax'
      ];

      validInstruments.forEach(instrument => {
        expect(() => {
          track.setInstrument(instrument);
          expect(track.getInstrument()).toBe(instrument);
        }).not.toThrow();
      });
    });
  });

  describe('track state consistency', () => {
    it('should maintain consistent state after multiple operations', () => {
      // Setup initial state
      track.setName('Piano Track');
      track.setInstrument('acoustic_grand_piano');
      track.setVolume(-3);

      const regions = [
        createMockMidiRegion({ id: 'r1', trackId: '0', name: 'Intro' }),
        createMockMidiRegion({ id: 'r2', trackId: '0', name: 'Verse' })
      ];
      track.setRegions(regions);

      // Verify initial state
      expect(track.getName()).toBe('Piano Track');
      expect(track.getInstrument()).toBe('acoustic_grand_piano');
      expect(track.getVolume()).toBe(-3);
      expect(track.getRegions()).toHaveLength(2);

      // Modify state
      track.setInstrument('electric_piano_1');
      track.addRegion(createMockMidiRegion({
        id: 'r3',
        trackId: '0',
        name: 'Chorus'
      }));

      // Verify modified state
      expect(track.getName()).toBe('Piano Track');
      expect(track.getInstrument()).toBe('electric_piano_1');
      expect(track.getVolume()).toBe(-3);
      expect(track.getRegions()).toHaveLength(3);
    });

    it('should handle region-track relationship correctly', () => {
      const region = createMockMidiRegion({ 
        id: 'test-region',
        trackId: track.getId().toString(),
        name: 'Test Region'
      });

      track.addRegion(region);
      
      // Verify region is in track
      expect(track.getRegions()).toContain(region);
      
      // Find region manually since getRegionById doesn't exist
      const foundRegion = track.getRegions().find(r => r.getId() === 'test-region');
      expect(foundRegion).toBe(region);
      
      // Verify region properties
      expect(region.getTrackId()).toBe(track.getId().toString());
    });
  });

  describe('edge cases and error handling', () => {
    it('should handle empty track name', () => {
      const emptyNameTrack = new KGMidiTrack('', 0, 'piano');
      expect(emptyNameTrack.getName()).toBe('');
    });

    it('should handle negative track ID', () => {
      const negativeIdTrack = new KGMidiTrack('Test', -1, 'piano');
      expect(negativeIdTrack.getId()).toBe(-1);
    });

    it('should handle volume boundaries', () => {
      track.setVolume(-60);
      expect(track.getVolume()).toBe(-60);

      track.setVolume(0);
      expect(track.getVolume()).toBe(0);

      track.setVolume(6);
      expect(track.getVolume()).toBe(6);

      // Volume outside valid dB range is clamped
      track.setVolume(10);
      expect(track.getVolume()).toBe(10);

      track.setVolume(-100);
      expect(track.getVolume()).toBe(-60);
    });

    it('should handle large number of regions', () => {
      const manyRegions = Array.from({ length: 100 }, (_, i) =>
        createMockMidiRegion({
          id: `region-${i}`,
          trackId: track.getId().toString(),
          name: `Region ${i}`
        })
      );

      track.setRegions(manyRegions);
      expect(track.getRegions()).toHaveLength(100);

      // Should be able to find any region
      const foundRegion50 = track.getRegions().find(r => r.getId() === 'region-50');
      const foundRegion99 = track.getRegions().find(r => r.getId() === 'region-99');
      
      expect(foundRegion50).toBeDefined();
      expect(foundRegion99).toBeDefined();
    });
  });

  describe('class-transformer compatibility', () => {
    it('should have proper type annotations for serialization', () => {
      // Verify the class has the necessary decorators for serialization
      expect(track.getCurrentType()).toBe('KGMidiTrack');
      
      // Test that default instrument fallback works
      const instrument = track.getInstrument();
      expect(instrument).toBe('acoustic_grand_piano');
    });

    it('should maintain regions type after serialization simulation', () => {
      const regions = [
        createMockMidiRegion({ id: 'r1', trackId: '0' }),
        createMockMidiRegion({ id: 'r2', trackId: '0' })
      ];

      track.setRegions(regions);
      
      // Simulate what happens during serialization/deserialization
      const retrievedRegions = track.getRegions();
      expect(retrievedRegions).toHaveLength(2);
      retrievedRegions.forEach(region => {
        expect(region).toBeInstanceOf(KGMidiRegion);
      });
    });
  });
});
