package matrix

import (
	"database/sql"
	"encoding/json"
	"log"

	"maunium.net/go/mautrix"
	"maunium.net/go/mautrix/event"
	"maunium.net/go/mautrix/id"
)

type sqlStore struct {
	db	*sql.DB
}

func (s sqlStore) CreateTables() error {
	tables := []string{
		`CREATE TABLE IF NOT EXISTS filter_ids (
			user_id		TEXT	PRIMARY KEY ON CONFLICT REPLACE,
			filter_id	TEXT	NOT NULL
		);
		`,
		`CREATE TABLE IF NOT EXISTS next_batch_tokens (
			user_id			TEXT	PRIMARY KEY ON CONFLICT REPLACE,
			next_batch_token	TEXT	NOT NULL
		);
		`,
		`CREATE TABLE IF NOT EXISTS rooms (
			room_id			TEXT	PRIMARY KEY ON CONFLICT REPLACE,
			encryption_event	TEXT
		);
		`,
		`CREATE TABLE IF NOT EXISTS room_members (
			room_id	TEXT,
			user_id	TEXT,

			PRIMARY KEY (room_id, user_id) ON CONFLICT IGNORE
		);
		`,
	}

	tx, err := s.db.Begin()
	if err != nil {
		return err
	}

	for _, table := range tables {
		_, err := tx.Exec(table)
		if err != nil {
			if err := tx.Rollback(); err != nil {
				log.Print(err)
			}

			return err
		}
	}

	err = tx.Commit()
	if err != nil {
		return err
	}

	return nil
}

func (s sqlStore) SaveFilterID(userID id.UserID, filterID string) {
	_, err := s.db.Exec("INSERT INTO filter_ids VALUES(?, ?);", userID, filterID)
	if err != nil {
		log.Print(err)
	}
}

func (s sqlStore) LoadFilterID(userID id.UserID) (filterID string) {
	row := s.db.QueryRow("SELECT filter_id FROM filter_ids WHERE user_id = ?;", userID)

	err := row.Scan(&filterID)
	if err != nil {
		return ""
	}

	return filterID
}

func (s sqlStore) SaveNextBatch(userID id.UserID, nextBatchToken string) {
	_, err := s.db.Exec("INSERT INTO next_batch_tokens VALUES(?, ?);", userID, nextBatchToken)
	if err != nil {
		log.Print(err)
	}
}

func (s sqlStore) LoadNextBatch(userID id.UserID) (nextBatchToken string) {
	row := s.db.QueryRow("SELECT next_batch_token FROM next_batch_tokens WHERE user_id = ?;", userID)

	err := row.Scan(&nextBatchToken)
	if err != nil {
		return ""
	}

	return nextBatchToken
}

func (s sqlStore) SaveRoom(room *mautrix.Room) {
}

func (s sqlStore) LoadRoom(roomID id.RoomID) *mautrix.Room {
	return mautrix.NewRoom(roomID)
}

func (s sqlStore) IsEncrypted(roomID id.RoomID) (isEncrypted bool) {
	row := s.db.QueryRow("SELECT encryption_event NOT NULL FROM rooms WHERE room_id = ?;", roomID)

	err := row.Scan(&isEncrypted)
	if err != nil {
		return false
	}

	return isEncrypted
}

func (s sqlStore) GetEncryptionEvent(roomID id.RoomID) (encryptionEvent *event.EncryptionEventContent) {
	row := s.db.QueryRow("SELECT encryption_event FROM rooms WHERE room_id = ?;", roomID)

	var data []byte
	if err := row.Scan(&data); err != nil {
		return nil
	}

	err := json.Unmarshal(data, encryptionEvent)
	if err != nil {
		return nil
	}

	return encryptionEvent
}

func (s sqlStore) FindSharedRooms(userID id.UserID) (sharedRooms []id.RoomID) {
	rows, err := s.db.Query("SELECT rooms.room_id FROM rooms, (SELECT room_id FROM room_members GROUP BY room_id HAVING COUNT(*) > 1) shared_rooms WHERE shared_rooms.room_id = rooms.room_id AND encryption_event NOT NULL;")
	if err != nil {
		return nil
	}
	defer rows.Close()

	for rows.Next() {
		var roomID string
		if err := rows.Scan(&roomID); err != nil {
			continue
		}

		sharedRooms = append(sharedRooms, id.RoomID(roomID))
	}
	if rows.Err() != nil {
		return nil
	}

	return sharedRooms
}

func (s sqlStore) FindAllSharedRooms(userID id.UserID) (sharedRooms []id.RoomID) {
	rows, err := s.db.Query("SELECT room_id FROM room_members GROUP BY room_id HAVING COUNT(*) > 1;")
	if err != nil {
		return nil
	}
	defer rows.Close()

	for rows.Next() {
		var roomID string
		if err := rows.Scan(&roomID); err != nil {
			continue
		}

		sharedRooms = append(sharedRooms, id.RoomID(roomID))
	}
	if rows.Err() != nil {
		return nil
	}

	return sharedRooms
}

func (s sqlStore) GetRoomMembers(roomID id.RoomID) (roomMembers []id.UserID) {
	rows, err := s.db.Query("SELECT user_id FROM room_members WHERE room_id = ?;", roomID)
	if err != nil {
		return nil
	}
	defer rows.Close()

	for rows.Next() {
		var userID string
		if err := rows.Scan(&userID); err != nil {
			continue
		}

		roomMembers = append(roomMembers, id.UserID(userID))
	}
	if rows.Err() != nil {
		return nil
	}

	return roomMembers
}

func (s sqlStore) SetMembership(roomID id.RoomID, userID string, membership event.Membership) {
	if membership.IsInviteOrJoin() {
		_, err := s.db.Exec("INSERT INTO room_members VALUES(?, ?);", roomID, userID)
		if err != nil {
			log.Print(err)
		}
	} else if membership.IsLeaveOrBan() {
		_, err := s.db.Exec("DELETE FROM room_members WHERE room_id = ? AND user_id = ?;", roomID, userID)
		if err != nil {
			log.Print(err)
		}
	}
}

func (s sqlStore) SetEncryptionEvent(roomID id.RoomID, encryptionEvent *event.EncryptionEventContent) {
	var data []byte
	if encryptionEvent != nil {
		var err error
		data, err = json.Marshal(encryptionEvent)
		if err != nil {
			log.Print(err)
			return
		}
	}

	_, err := s.db.Exec("INSERT INTO rooms VALUES(?, ?);", roomID, data)
	if err != nil {
		log.Print(err)
	}
}
