package book

import (
	"context"
	"server/internal/dao"
	"server/internal/model"
	"server/internal/model/do"
	"server/internal/service"
	"server/utility/ecode"

	"github.com/gogf/gf/v2/database/gdb"
)

type sBook struct{}

func New() service.IBook {
	return &sBook{}
}

func init() {
	service.RegisterBook(New())
}

// List retrieves a paginated list of books
func (s *sBook) List(ctx context.Context, in *model.BookListIn) (out *model.BookListOut, err error) {
	out = &model.BookListOut{}
	m := dao.Books.Ctx(ctx)
	if in.Title != "" {
		m = m.Where(dao.Books.Columns().Title+" like ?", "%"+in.Title+"%")
	}
	if in.CategoryId != 0 {
		m = m.Where(dao.Books.Columns().CategoryId, in.CategoryId)
	}
	if in.AuthorId != 0 {
		m = m.Where(dao.Books.Columns().AuthorId, in.AuthorId)
	}
	if in.Status != 0 {
		m = m.Where(dao.Books.Columns().Status, in.Status)
	}
	if in.IsRecommended != 0 {
		m = m.Where(dao.Books.Columns().IsRecommended, in.IsRecommended)
	}
	if in.Sort != "" {
		m = m.Order(in.Sort)
	}
	if err = m.Page(in.Page, in.Size).WithAll().ScanAndCount(&out.List, &out.Total, false); err != nil {
		return
	}
	return
}

func (s *sBook) Create(ctx context.Context, in *model.BookAddIn) (out *model.BookCRUDOut, err error) {
	exist, err := dao.Books.Ctx(ctx).
		Where(dao.Books.Columns().Title, in.Title).
		Exist()
	if err != nil {
		return nil, ecode.Fail.Sub("book_query_failed")
	}
	if exist {
		return nil, ecode.Params.Sub("book_exists")
	}

	if _, err := dao.Books.Ctx(ctx).Data(do.Books{
		AuthorId:      in.AuthorId,
		CategoryId:    in.CategoryId,
		Title:         in.Title,
		CoverUrl:      in.CoverUrl,
		Description:   in.Description,
		Status:        in.Status,
		Tags:          in.Tags,
		IsRecommended: in.IsRecommended,
	}).Insert(); err != nil {
		return nil, ecode.Fail.Sub("book_create_failed")
	}

	return &model.BookCRUDOut{
		Success: true,
	}, nil
}

func (s *sBook) Update(ctx context.Context, in *model.BookEditIn) (out *model.BookCRUDOut, err error) {
	exist, err := dao.Books.Ctx(ctx).
		WherePri(in.Id).
		Exist()
	if err != nil {
		return nil, ecode.Fail.Sub("book_query_failed")
	}
	if !exist {
		return nil, ecode.NotFound.Sub("book_not_found")
	}

	exist, err = dao.Books.Ctx(ctx).
		Where(dao.Books.Columns().Title, in.Title).
		Where("id != ?", in.Id).
		Exist()
	if err != nil {
		return nil, ecode.Fail.Sub("book_query_failed")
	}
	if exist {
		return nil, ecode.Params.Sub("book_exists")
	}

	_, err = dao.Books.Ctx(ctx).
		WherePri(in.Id).
		Data(do.Books{
			AuthorId:      in.AuthorId,
			CategoryId:    in.CategoryId,
			Title:         in.Title,
			CoverUrl:      in.CoverUrl,
			Description:   in.Description,
			Status:        in.Status,
			Tags:          in.Tags,
			IsRecommended: in.IsRecommended,
		}).Update()
	if err != nil {
		return nil, ecode.Fail.Sub("book_update_failed")
	}

	return &model.BookCRUDOut{
		Success: true,
	}, nil
}

func (s *sBook) Delete(ctx context.Context, in *model.BookDelIn) (out *model.BookCRUDOut, err error) {
	exist, err := dao.Books.Ctx(ctx).
		WherePri(in.Id).
		Exist()
	if err != nil {
		return nil, ecode.Fail.Sub("book_query_failed")
	}
	if !exist {
		return nil, ecode.NotFound.Sub("book_not_found")
	}

	// 开启事务，删除书籍及相关数据
	err = dao.Books.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		// 1. 删除书籍相关的章节
		_, err := dao.Chapters.Ctx(ctx).TX(tx).
			Where(dao.Chapters.Columns().BookId, in.Id).
			Delete()
		if err != nil {
			return ecode.Fail.Sub("chapter_delete_failed")
		}

		// 2. 删除书籍相关的用户阅读记录
		_, err = dao.UserReadRecords.Ctx(ctx).TX(tx).
			Where(dao.UserReadRecords.Columns().BookId, in.Id).
			Delete()
		if err != nil {
			return ecode.Fail.Sub("read_record_delete_failed")
		}

		// 3. 删除书籍相关的用户阅读历史
		_, err = dao.UserReadHistory.Ctx(ctx).TX(tx).
			Where(dao.UserReadHistory.Columns().BookId, in.Id).
			Delete()
		if err != nil {
			return ecode.Fail.Sub("history_delete_failed")
		}

		// 4. 删除书籍相关的用户书架
		_, err = dao.Bookshelves.Ctx(ctx).TX(tx).
			Where(dao.Bookshelves.Columns().BookId, in.Id).
			Delete()
		if err != nil {
			return ecode.Fail.Sub("bookshelf_delete_failed")
		}

		// 5. 删除书籍相关的用户评分
		_, err = dao.BookRatings.Ctx(ctx).TX(tx).
			Where(dao.BookRatings.Columns().BookId, in.Id).
			Delete()
		if err != nil {
			return ecode.Fail.Sub("rating_delete_failed")
		}

		// 6. 删除书籍相关的章节购买记录
		_, err = dao.UserChapterPurchases.Ctx(ctx).TX(tx).
			Where(dao.UserChapterPurchases.Columns().BookId, in.Id).
			Delete()
		if err != nil {
			return ecode.Fail.Sub("purchase_delete_failed")
		}

		// 7. 最后删除书籍
		_, err = dao.Books.Ctx(ctx).TX(tx).WherePri(in.Id).Delete()
		if err != nil {
			return ecode.Fail.Sub("book_delete_failed")
		}

		return nil
	})

	if err != nil {
		return nil, err
	}

	return &model.BookCRUDOut{
		Success: true,
	}, nil
}

// AppList retrieves book list for app
func (s *sBook) AppList(ctx context.Context, in *model.BookAppListIn) (out *model.BookAppListOut, err error) {
	out = &model.BookAppListOut{}

	// 构建查询条件，使用WithAll查询关联的作者信息
	m := dao.Books.Ctx(ctx).WithAll()

	// 书名模糊搜索
	if in.Title != "" {
		m = m.Where(dao.Books.Columns().Title+" like ?", "%"+in.Title+"%")
	}

	// 分类筛选
	if in.CategoryId != 0 {
		m = m.Where(dao.Books.Columns().CategoryId, in.CategoryId)
	}

	// 推荐筛选
	if in.IsRecommended {
		m = m.Where(dao.Books.Columns().IsRecommended, 1)
	}

	// 最新筛选（按创建时间倒序）
	if in.IsLatest == 1 {
		m = m.OrderDesc(dao.Books.Columns().CreatedAt)
	}
	if in.AuthorId != 0 {
		m = m.Where(dao.Books.Columns().AuthorId, in.AuthorId)
	}
	if in.IsFeatured {
		m = m.Where(dao.Books.Columns().IsFeatured, 1)
	}
	if in.IsHot {
		m = m.Where(dao.Books.Columns().IsHot, 1)
	}
	if in.Sort != "" {
		m = m.Order(in.Sort)
	}

	// 执行分页查询
	if err = m.Page(in.Page, in.Size).ScanAndCount(&out.List, &out.Total, false); err != nil {
		return nil, ecode.Fail.Sub("book_query_failed")
	}

	// 用户评分批量查询
	if in.UserId > 0 && len(out.List) > 0 {
		bookIds := make([]int64, 0, len(out.List))
		for _, item := range out.List {
			bookIds = append(bookIds, item.Id)
		}
		// 查询用户对这些书的评分
		type ratingRow struct {
			BookId int64   `json:"bookId"`
			Score  float64 `json:"score"`
		}
		ratings := make([]ratingRow, 0)
		err = dao.BookRatings.Ctx(ctx).
			Fields("book_id, score").
			Where(dao.BookRatings.Columns().UserId, in.UserId).
			WhereIn(dao.BookRatings.Columns().BookId, bookIds).
			Scan(&ratings)
		if err == nil && len(ratings) > 0 {
			ratingMap := make(map[int64]float64, len(ratings))
			for _, r := range ratings {
				ratingMap[r.BookId] = r.Score
			}
			for i := range out.List {
				if score, ok := ratingMap[out.List[i].Id]; ok {
					out.List[i].HasRated = true
					out.List[i].MyRating = score
				} else {
					out.List[i].HasRated = false
					out.List[i].MyRating = 0
				}
			}
		} else {
			for i := range out.List {
				out.List[i].HasRated = false
				out.List[i].MyRating = 0
			}
		}

		// 查询用户书架
		type shelfRow struct {
			BookId int64 `json:"bookId"`
		}
		shelves := make([]shelfRow, 0)
		err = dao.Bookshelves.Ctx(ctx).
			Fields("book_id").
			Where(dao.Bookshelves.Columns().UserId, in.UserId).
			WhereIn(dao.Bookshelves.Columns().BookId, bookIds).
			Scan(&shelves)
		if err == nil && len(shelves) > 0 {
			shelfMap := make(map[int64]bool, len(shelves))
			for _, s := range shelves {
				shelfMap[s.BookId] = true
			}
			for i := range out.List {
				out.List[i].IsInBookshelf = shelfMap[out.List[i].Id]
			}
		} else {
			for i := range out.List {
				out.List[i].IsInBookshelf = false
			}
		}
	}

	return out, nil
}

// AppRate rates a book for app
func (s *sBook) AppRate(ctx context.Context, in *model.BookAppRateIn) (out *model.BookAppRateOut, err error) {
	out = &model.BookAppRateOut{}

	// 必须指定用户ID
	if in.UserId == 0 {
		return nil, ecode.Fail.Sub("user_id_required")
	}

	// 必须指定书籍ID
	if in.BookId == 0 {
		return nil, ecode.Fail.Sub("book_id_required")
	}

	// 验证评分范围（1-10分）
	if in.Rating < 1 || in.Rating > 10 {
		return nil, ecode.Fail.Sub("rating_invalid")
	}

	// 检查书籍是否存在
	exist, err := dao.Books.Ctx(ctx).Where(dao.Books.Columns().Id, in.BookId).Exist()
	if err != nil {
		return nil, ecode.Fail.Sub("book_query_failed")
	}
	if !exist {
		return nil, ecode.NotFound.Sub("book_not_found")
	}

	// 检查用户是否存在
	exist, err = dao.Users.Ctx(ctx).Where(dao.Users.Columns().Id, in.UserId).Exist()
	if err != nil {
		return nil, ecode.Fail.Sub("user_query_failed")
	}
	if !exist {
		return nil, ecode.NotFound.Sub("user_not_found")
	}

	// 开启事务处理评分
	if err := dao.BookRatings.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		// 检查是否已经评分过
		exist, err := dao.BookRatings.Ctx(ctx).
			Where(dao.BookRatings.Columns().UserId, in.UserId).
			Where(dao.BookRatings.Columns().BookId, in.BookId).
			Exist()
		if err != nil {
			return ecode.Fail.Sub("rating_query_failed")
		}

		if exist {
			// 更新现有评分
			_, err = dao.BookRatings.Ctx(ctx).
				Where(dao.BookRatings.Columns().UserId, in.UserId).
				Where(dao.BookRatings.Columns().BookId, in.BookId).
				Data(do.BookRatings{
					Score: in.Rating,
				}).Update()
			if err != nil {
				return ecode.Fail.Sub("rating_update_failed")
			}
		} else {
			// 创建新评分记录
			_, err = dao.BookRatings.Ctx(ctx).Data(do.BookRatings{
				UserId: in.UserId,
				BookId: in.BookId,
				Score:  in.Rating,
			}).Insert()
			if err != nil {
				return ecode.Fail.Sub("rating_create_failed")
			}
		}

		// 重新计算书籍平均评分
		var result struct {
			AvgRating float64 `json:"avg_rating"`
		}
		err = dao.BookRatings.Ctx(ctx).
			Where(dao.BookRatings.Columns().BookId, in.BookId).
			Fields("COALESCE(AVG(" + dao.BookRatings.Columns().Score + "), 0) as avg_rating").
			Scan(&result)
		if err != nil {
			return ecode.Fail.Sub("rating_calculation_failed")
		}

		// 更新书籍的平均评分
		_, err = dao.Books.Ctx(ctx).
			Where(dao.Books.Columns().Id, in.BookId).
			Data(do.Books{
				Rating: result.AvgRating,
			}).Update()
		if err != nil {
			return ecode.Fail.Sub("book_rating_update_failed")
		}

		return nil
	}); err != nil {
		return nil, err
	}

	out.Success = true
	return out, nil
}

// AppDetail retrieves book detail for app
func (s *sBook) AppDetail(ctx context.Context, in *model.BookAppDetailIn) (out *model.BookAppDetailOut, err error) {
	out = &model.BookAppDetailOut{}

	// 必须指定书籍ID
	if in.Id == 0 {
		return nil, ecode.Fail.Sub("book_id_required")
	}

	// 构建查询条件
	m := dao.Books.Ctx(ctx)
	m = m.Where(dao.Books.Columns().Id, in.Id)

	// 执行查询
	if err = m.WithAll().Scan(out); err != nil {
		return nil, ecode.Fail.Sub("book_query_failed")
	}

	// 检查书籍是否存在
	if out.Id == 0 {
		return nil, ecode.NotFound.Sub("book_not_found")
	}

	// 如果用户已登录，查询阅读进度
	if in.UserId > 0 {
		// 查询用户是否已将本书加入书架
		count, err := dao.Bookshelves.Ctx(ctx).
			Where(dao.Bookshelves.Columns().UserId, in.UserId).
			Where(dao.Bookshelves.Columns().BookId, in.Id).
			Count()
		if err == nil {
			out.IsInBookshelf = count > 0
		}

		// 查询用户是否已评分
		var ratingRecord struct {
			Score float64 `json:"score"`
		}
		err = dao.BookRatings.Ctx(ctx).
			Fields("score").
			Where(dao.BookRatings.Columns().UserId, in.UserId).
			Where(dao.BookRatings.Columns().BookId, in.Id).
			Scan(&ratingRecord)
		if err == nil && ratingRecord.Score > 0 {
			out.HasRated = true
			out.MyRating = ratingRecord.Score
		}

		// 查询用户对该书籍的历史记录
		var historyRecord struct {
			ChapterId int64  `json:"chapterId"`
			ReadAt    string `json:"readAt"`
		}
		err = dao.UserReadHistory.Ctx(ctx).
			Fields("chapter_id, read_at").
			Where(dao.UserReadHistory.Columns().UserId, in.UserId).
			Where(dao.UserReadHistory.Columns().BookId, in.Id).
			OrderDesc(dao.UserReadHistory.Columns().ReadAt).
			Scan(&historyRecord)

		if err == nil && historyRecord.ChapterId > 0 {
			// 用户读过这本书
			out.HasRead = true
			out.LastChapterId = historyRecord.ChapterId
			out.LastReadAt = historyRecord.ReadAt

			// 计算阅读进度：总章节数 vs 已读章节数
			totalChapters, err := dao.Chapters.Ctx(ctx).
				Where(dao.Chapters.Columns().BookId, in.Id).
				Count()

			if err == nil && totalChapters > 0 {
				readChapters, err := dao.UserReadRecords.Ctx(ctx).
					Where(dao.UserReadRecords.Columns().UserId, in.UserId).
					Where(dao.UserReadRecords.Columns().BookId, in.Id).
					WhereGT(dao.UserReadRecords.Columns().ChapterId, 0). // 只统计有章节ID的记录
					Count()

				if err == nil {
					out.ReadProgress = int(float64(readChapters) / float64(totalChapters) * 100)
					if out.ReadProgress > 100 {
						out.ReadProgress = 100
					}
				}
			}
		}
	}

	return out, nil
}

func (s *sBook) MyList(ctx context.Context, in *model.MyBookListIn) (out *model.MyBookListOut, err error) {
	out = &model.MyBookListOut{}

	if in.UserId == 0 {
		return nil, ecode.Fail.Sub("user_id_required")
	}
	if in.Type < 1 || in.Type > 3 {
		return nil, ecode.Fail.Sub("type_invalid")
	}

	var (
		ids      []int64
		extraMap map[int64]struct {
			Progress   int
			LastReadAt string
		}
		total int
	)

	switch in.Type {
	case 1, 2:
		var bookshelves []struct {
			BookId          int64   `json:"bookId"`
			LastReadPercent float64 `json:"lastReadPercent"`
			LastReadAt      string  `json:"lastReadAt"`
		}
		readStatus := 1
		if in.Type == 2 {
			readStatus = 2
		}
		q := dao.Bookshelves.Ctx(ctx).
			Fields("book_id, last_read_percent, last_read_at").
			Where(dao.Bookshelves.Columns().UserId, in.UserId).
			Where(dao.Bookshelves.Columns().ReadStatus, readStatus)
		if in.Sort != "" {
			q = q.Order(in.Sort)
		} else {
			q = q
		}
		err = q.Page(in.Page, in.Size).ScanAndCount(&bookshelves, &total, false)
		if err != nil {
			return nil, ecode.Fail.Sub("bookshelf_query_failed")
		}
		ids = make([]int64, 0, len(bookshelves))
		extraMap = make(map[int64]struct {
			Progress   int
			LastReadAt string
		}, len(bookshelves))
		for _, bs := range bookshelves {
			ids = append(ids, bs.BookId)
			extraMap[bs.BookId] = struct {
				Progress   int
				LastReadAt string
			}{
				Progress:   int(bs.LastReadPercent),
				LastReadAt: bs.LastReadAt,
			}
		}
	case 3:
		var histories []struct {
			BookId    int64  `json:"bookId"`
			ChapterId int64  `json:"chapterId"`
			ReadAt    string `json:"readAt"`
		}
		q := dao.UserReadHistory.Ctx(ctx).
			Fields("book_id, chapter_id, read_at").
			Where(dao.UserReadHistory.Columns().UserId, in.UserId)
		if in.Sort != "" {
			q = q.Order(in.Sort)
		} else {
			q = q.OrderDesc(dao.UserReadHistory.Columns().ReadAt)
		}
		err = q.Page(in.Page, in.Size).ScanAndCount(&histories, &total, false)
		if err != nil {
			return nil, ecode.Fail.Sub("history_query_failed")
		}
		ids = make([]int64, 0, len(histories))
		extraMap = make(map[int64]struct {
			Progress   int
			LastReadAt string
		}, len(histories))
		for _, h := range histories {
			ids = append(ids, h.BookId)
			extraMap[h.BookId] = struct {
				Progress   int
				LastReadAt string
			}{
				Progress:   0,
				LastReadAt: h.ReadAt,
			}
		}
	}

	var books []model.MyBookItem
	if len(ids) > 0 {
		err = dao.Books.Ctx(ctx).WhereIn(dao.Books.Columns().Id, ids).Scan(&books)
		if err != nil {
			return nil, ecode.Fail.Sub("book_query_failed")
		}
	}

	// type3 需要查书架
	shelfBookIds := map[int64]bool{}
	if in.Type == 3 && len(ids) > 0 {
		var bookshelves []struct{ BookId int64 }
		err = dao.Bookshelves.Ctx(ctx).
			Fields("book_id").
			Where(dao.Bookshelves.Columns().UserId, in.UserId).
			WhereIn(dao.Bookshelves.Columns().BookId, ids).
			Scan(&bookshelves)
		if err != nil {
			return nil, ecode.Fail.Sub("bookshelf_query_failed")
		}
		for _, bs := range bookshelves {
			shelfBookIds[bs.BookId] = true
		}
	}

	for i := range books {
		if info, ok := extraMap[books[i].Id]; ok {
			books[i].Progress = info.Progress
			books[i].LastReadAt = info.LastReadAt
		}
		if in.Type == 1 || in.Type == 2 {
			books[i].IsInShelf = true
		} else if in.Type == 3 {
			books[i].IsInShelf = shelfBookIds[books[i].Id]
		}
	}

	// 查询用户评分信息
	if len(books) > 0 {
		bookIds := make([]int64, 0, len(books))
		for _, book := range books {
			bookIds = append(bookIds, book.Id)
		}

		type ratingRow struct {
			BookId int64   `json:"bookId"`
			Score  float64 `json:"score"`
		}
		ratings := make([]ratingRow, 0)
		err = dao.BookRatings.Ctx(ctx).
			Fields("book_id, score").
			Where(dao.BookRatings.Columns().UserId, in.UserId).
			WhereIn(dao.BookRatings.Columns().BookId, bookIds).
			Scan(&ratings)
		if err == nil && len(ratings) > 0 {
			ratingMap := make(map[int64]float64, len(ratings))
			for _, r := range ratings {
				ratingMap[r.BookId] = r.Score
			}
			for i := range books {
				if score, ok := ratingMap[books[i].Id]; ok {
					books[i].HasRated = true
					books[i].MyRating = score
				} else {
					books[i].HasRated = false
					books[i].MyRating = 0
				}
			}
		} else {
			for i := range books {
				books[i].HasRated = false
				books[i].MyRating = 0
			}
		}
	}

	out.Total = total
	out.List = books
	return
}

// SetFeatured: 单独修改书籍的精选状态
func (s *sBook) SetFeatured(ctx context.Context, in *model.BookSetFeaturedIn) (out *model.BookCRUDOut, err error) {
	// 检查书籍是否存在
	exist, err := dao.Books.Ctx(ctx).WherePri(in.Id).Exist()
	if err != nil {
		return nil, ecode.Fail.Sub("book_query_failed")
	}
	if !exist {
		return nil, ecode.NotFound.Sub("book_not_found")
	}
	_, err = dao.Books.Ctx(ctx).WherePri(in.Id).Data(do.Books{
		IsFeatured: in.IsFeatured,
	}).Update()
	if err != nil {
		return nil, ecode.Fail.Sub("book_update_failed")
	}
	return &model.BookCRUDOut{Success: true}, nil
}

// SetRecommended: 单独修改书籍的推荐状态
func (s *sBook) SetRecommended(ctx context.Context, in *model.BookSetRecommendedIn) (out *model.BookCRUDOut, err error) {
	// 检查书籍是否存在
	exist, err := dao.Books.Ctx(ctx).WherePri(in.Id).Exist()
	if err != nil {
		return nil, ecode.Fail.Sub("book_query_failed")
	}
	if !exist {
		return nil, ecode.NotFound.Sub("book_not_found")
	}
	_, err = dao.Books.Ctx(ctx).WherePri(in.Id).Data(do.Books{
		IsRecommended: in.IsRecommended,
	}).Update()
	if err != nil {
		return nil, ecode.Fail.Sub("book_update_failed")
	}
	return &model.BookCRUDOut{Success: true}, nil
}

// SetHot: 单独修改书籍的热门状态
func (s *sBook) SetHot(ctx context.Context, in *model.BookSetHotIn) (out *model.BookCRUDOut, err error) {
	// 检查书籍是否存在
	exist, err := dao.Books.Ctx(ctx).WherePri(in.Id).Exist()
	if err != nil {
		return nil, ecode.Fail.Sub("book_query_failed")
	}
	if !exist {
		return nil, ecode.NotFound.Sub("book_not_found")
	}
	_, err = dao.Books.Ctx(ctx).WherePri(in.Id).Data(do.Books{
		IsHot: in.IsHot,
	}).Update()
	if err != nil {
		return nil, ecode.Fail.Sub("book_update_failed")
	}
	return &model.BookCRUDOut{Success: true}, nil
}
