describe('RailsResource.snapshots', function () {
    'use strict';
    var Book, $httpBackend, railsResourceFactory, railsSerializer;

    beforeEach(function () {
        module('rails')
    });

    beforeEach(inject(function (_$httpBackend_, _railsResourceFactory_, _railsSerializer_) {
        $httpBackend = _$httpBackend_;
        railsResourceFactory = _railsResourceFactory_;
        railsSerializer = _railsSerializer_;
        Book = railsResourceFactory({
            url: '/books',
            name: 'book',
            extensions: ['snapshots']
        });
    }));

    afterEach(function() {
        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
    });

    it('should store all keys', function () {
        var book, data = {id: 1, $key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        expect(book.snapshot()).toBe(0);

        expect(book.$snapshots).toBeDefined();
        expect(book.$snapshots.length).toBe(1);
        expect(book.$snapshots[0].$snapshots).toBeUndefined();
        expect(book).toEqualData(data);
        expect(book.$snapshots[0].$key).toBe('1234');
        expect(book.$snapshots[0]).toEqualData(data);
    });

    it('should store deep copy', function () {
        var book, data = {
            id: 1,
            $key: '1234',
            name: 'The Winds of Winter',
            author: {
                id: 1,
                name: 'George R. R. Martin'
            }
        };

        book = new Book(data);
        book.snapshot();

        expect(book.$snapshots[0].author).toBeDefined();
        expect(book.$snapshots[0].author.id).toBe(1);
        expect(book.$snapshots[0]).toEqualData(data);
    });

    it('should store multiple snapshots', function () {
        var book, data = {id: 1, $key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        expect(book.snapshot()).toBe(0);
        book.$key = '1235';
        expect(book.snapshot()).toBe(1);
        book.$key = '1236';
        expect(book.snapshot()).toBe(2);

        expect(book.$snapshots).toBeDefined();
        expect(book.$snapshots.length).toBe(3);
        expect(book.$snapshots[0].$key).toBe('1234');
        expect(book.$snapshots[1].$key).toBe('1235');
        expect(book.$snapshots[2].$key).toBe('1236');
    });

    it('should rollback single version', function () {
        var book, data = {id: 1, $key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        book.snapshot();
        book.$key = '1235';
        book.snapshot();
        book.$key = '1236';
        book.rollback();

        expect(book.$key).toBe('1235');
        expect(book.$snapshots).toBeDefined();
        expect(book.$snapshots.length).toBe(1);
    });

    it('should rollback deep copy', function () {
        var book, data = {
            id: 1,
            $key: '1234',
            name: 'The Winds of Winter',
            author: {
                id: 1,
                name: 'George R. R. Martin'
            }
        };

        book = new Book(data);
        book.snapshot();
        book.author = {id: 2, name: 'Hugh Howey'};
        book.rollback();

        expect(book.author).toBeDefined();
        expect(book).toEqualData(data);

        book.snapshot();
        book.author.name = 'Hugh Howey';
        book.rollback();

        expect(book.author).toBeDefined();
        expect(book).toEqualData(data);
    });

    it('should not modify source nested object on rollback', function () {
        var book, hughHoweyAuthor = {id: 2, name: 'Hugh Howey'},
            data = {
            id: 1,
            $key: '1234',
            name: 'The Winds of Winter',
            author: {
                id: 1,
                name: 'George R. R. Martin'
            }
        };

        book = new Book(data);
        book.snapshot();
        book.author = hughHoweyAuthor;
        book.rollback();

        expect(hughHoweyAuthor).toEqualData({id: 2, name: 'Hugh Howey'});
    });


    it('should allow multiple rollbacks', function () {
        var book, data = {id: 1, $key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        book.snapshot();
        book.$key = '1235';
        book.snapshot();
        book.$key = '1236';
        book.rollback();

        expect(book.$key).toBe('1235');
        expect(book.$snapshots).toBeDefined();
        expect(book.$snapshots.length).toBe(1);
        book.rollback();

        expect(book.$key).toBe('1234');
        expect(book.$snapshots).toBeDefined();
        expect(book.$snapshots.length).toBe(0);
    });


    it('should rollback specified number of versions', function () {
        var book, data = {id: 1, $key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        book.snapshot();
        book.$key = '1235';
        book.snapshot();
        book.$key = '1236';
        book.snapshot();
        book.$key = '1237';
        book.rollback(2);

        expect(book.$key).toBe('1235');
        expect(book.$snapshots).toBeDefined();
        expect(book.$snapshots.length).toBe(1);
    });

    it('should not change resource on rollback if no snapshots saved', function () {
        var book, data = {id: 1, $key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        book.$key = '1235';
        book.rollback();

        expect(book.$key).toBe('1235');
    });

    it('should roll back to the first snapshot for -1', function () {
        var book, data = {id: 1, $key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        book.snapshot();
        book.$key = '1235';
        book.snapshot();
        book.$key = '1236';
        book.snapshot();
        book.rollback(-1);

        expect(book.$key).toBe('1234');
        expect(book.$snapshots).toBeDefined();
        expect(book.$snapshots.length).toBe(0);
    });

    it('should roll back to the first snapshot when versions exceeds available snapshots', function () {
        var book, data = {id: 1, $key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        book.snapshot();
        book.$key = '1235';
        book.snapshot();
        book.$key = '1236';
        book.snapshot();
        book.rollback(1000);

        expect(book.$key).toBe('1234');
        expect(book.$snapshots).toBeDefined();
        expect(book.$snapshots.length).toBe(0);
    });

    it('should roll back to version in middle', function () {
        var book, data = {id: 1, $key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        book.snapshot();
        book.$key = '1235';
        book.snapshot();
        book.$key = '1236';
        book.snapshot();
        book.rollbackTo(1);

        expect(book.$key).toBe('1235');
        expect(book.$snapshots).toBeDefined();
        expect(book.$snapshots.length).toBe(1);
    });

    it('should roll back to first version', function () {
        var book, data = {id: 1, $key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        book.snapshot();
        book.$key = '1235';
        book.snapshot();
        book.$key = '1236';
        book.snapshot();
        book.rollbackTo(0);

        expect(book.$key).toBe('1234');
        expect(book.$snapshots).toBeDefined();
        expect(book.$snapshots.length).toBe(0);
    });

    it('should roll back to last version when version exceeds available versions', function () {
        var book, data = {id: 1, $key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        book.snapshot();
        book.$key = '1235';
        book.snapshot();
        book.$key = '1236';
        book.snapshot();
        book.$key = '1237';
        book.rollbackTo(100);

        expect(book.$key).toBe('1236');
        expect(book.$snapshots).toBeDefined();
        expect(book.$snapshots.length).toBe(2);
    });

    it('should reset snapshots on create', function () {
        var book, data = {$key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        book.snapshot();
        book.$key = '1235';

        $httpBackend.expectPOST('/books').respond(200, {book: {id: 1}});
        book.save();
        $httpBackend.flush();

        expect(book.$key).toBe('1235');
        expect(book.$snapshots).toBeDefined();
        expect(book.$snapshots.length).toBe(0);
    });

    it('should be able to save after rollback', function () {
        var book, data = {$key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        book.snapshot();
        book.$key = '1235';
        book.rollback();

        $httpBackend.expectPOST('/books').respond(200, {book: {id: 1}});
        book.save();
        $httpBackend.flush();

        expect(book.$key).toBe('1234');
        expect(book.$snapshots).toBeDefined();
        expect(book.$snapshots.length).toBe(0);
    });

    it('should reset snapshots on update', function () {
        var book, data = {id: 1, $key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        book.snapshot();
        book.$key = '1235';

        $httpBackend.expectPUT('/books/1').respond(200, {book: {id: 1}});
        book.save();
        $httpBackend.flush();

        expect(book.$key).toBe('1235');
        expect(book.$snapshots).toBeDefined();
        expect(book.$snapshots.length).toBe(0);
    });

    it('should reset snapshots on delete', function () {
        var book, data = {id: 1, $key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        book.snapshot();
        book.$key = '1235';

        $httpBackend.expectDELETE('/books/1').respond(200, {book: {id: 1}});
        book.delete();
        $httpBackend.flush();

        expect(book.$key).toBe('1235');
        expect(book.$snapshots).toBeDefined();
        expect(book.$snapshots.length).toBe(0);
    });

    it('should call rollback callback on rollback', function () {
        var book, callbackCalled = false, data = {id: 1, $key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        book.snapshot(function () {
            callbackCalled = true;
        });
        book.rollback();
        expect(callbackCalled).toBe(true);
    });

    it('should call correct rollback callback on each rollback', function () {
        var book, firstCallbackCalled = false, secondCallbackCalled = false,
            data = {id: 1, $key: '1234', name: 'The Winds of Winter'};
        book = new Book(data);
        book.snapshot(function () {
            firstCallbackCalled = true;
        });
        book.$key = '1235';
        book.snapshot(function () {
            secondCallbackCalled = true;
        });
        book.$key = '1236';

        book.rollback();
        expect(book.$key).toBe('1235');
        expect(firstCallbackCalled).toBe(false);
        expect(secondCallbackCalled).toBe(true);

        book.rollback();
        expect(book.$key).toBe('1234');
        expect(firstCallbackCalled).toBe(true);
        expect(secondCallbackCalled).toBe(true);
    });

    it('should detect unsnapped changes', function () {
        var book, data = {id: 1, $key: '1234', name: 'The Winds of Winter', theAuthor: 'George R.R. Martin'};
        book = new Book(data);
        expect(book.unsnappedChanges()).toBe(true);

        book.snapshot();
        expect(book.unsnappedChanges()).toBe(false);

        book.name = 'Harry Potter';
        expect(book.unsnappedChanges()).toBe(true);

        book.snapshot();
        expect(book.unsnappedChanges()).toBe(false);

        book.theAuthor = 'J.K. Rowling';
        expect(book.unsnappedChanges()).toBe(true);

        book.snapshot();
        expect(book.unsnappedChanges()).toBe(false);

        // angular.equals ignores $-prefixed properties
        book.$key = '1235'
        expect(book.unsnappedChanges()).toBe(false);
    });

    describe('serializer', function () {
        beforeEach(function () {
            Book.configure({
                serializer: railsSerializer(function () {
                    this.exclude('author');
                })
            });
        });

        it('should exclude author from snapshot and rollback', function () {
            var book, data = {
                id: 1,
                $key: '1234',
                name: 'The Winds of Winter',
                author: {
                    id: 1,
                    name: 'George R. R. Martin'
                }
            };

            book = new Book(data);
            book.snapshot();
            book.author.name = 'George Orwell';

            expect(book.$snapshots[0].author).not.toBeDefined();
            expect(book.author).toEqualData({id: 1, name: 'George Orwell'});

            book.rollback();
            // should still be George Orwell since it wasn't snapshotted
            expect(book.author).toEqualData({id: 1, name: 'George Orwell'});
        });

    });

    describe('snapshotSerializer', function () {
        beforeEach(function () {
            Book.configure({
                serializer: railsSerializer(function () {
                    this.exclude('author');
                }),
                snapshotSerializer: railsSerializer(function () {
                    this.exclude('$key');
                })
            });
        });

        it('should include author and exclude $key from snapshot and rollback', function () {
            var book, data = {
                id: 1,
                $key: '1234',
                name: 'The Winds of Winter',
                author: {
                    id: 1,
                    name: 'George R. R. Martin'
                }
            };

            book = new Book(data);
            book.snapshot();
            book.$key = '1235';
            book.author.name = 'George Orwell';

            expect(book.$snapshots[0].author).toBeDefined();
            expect(book.$snapshots[0].$key).not.toBeDefined();
            expect(book.$key).toBe('1235');
            expect(book.author).toEqualData({id: 1, name: 'George Orwell'});

            book.rollback();
            // should be 1235 since it wasn't snapshotted
            expect(book.$key).toBe('1235');
            // should be George R. R. Martin since it was snapshotted
            expect(book.author).toEqualData({id: 1, name: 'George R. R. Martin'});
        });

    });
});