package service

import (
	"bytes"
	"context"
	"encoding/csv"
	"fmt"
	"time"

	"github.com/xuri/excelize/v2"

	"github.com/user-management-system/internal/domain"
	"github.com/user-management-system/internal/pagination"
	"github.com/user-management-system/internal/repository"
)

// LoginLogService 登录日志服务
type LoginLogService struct {
	loginLogRepo *repository.LoginLogRepository
}

// NewLoginLogService 创建登录日志服务
func NewLoginLogService(loginLogRepo *repository.LoginLogRepository) *LoginLogService {
	return &LoginLogService{loginLogRepo: loginLogRepo}
}

// RecordLogin 记录登录日志
func (s *LoginLogService) RecordLogin(ctx context.Context, req *RecordLoginRequest) error {
	log := &domain.LoginLog{
		LoginType:  req.LoginType,
		DeviceID:   req.DeviceID,
		IP:         req.IP,
		Location:   req.Location,
		Status:     req.Status,
		FailReason: req.FailReason,
	}
	if req.UserID != 0 {
		log.UserID = &req.UserID
	}
	return s.loginLogRepo.Create(ctx, log)
}

// RecordLoginRequest 记录登录请求
type RecordLoginRequest struct {
	UserID     int64  `json:"user_id"`
	LoginType  int    `json:"login_type"` // 1-用户名, 2-邮箱, 3-手机
	DeviceID   string `json:"device_id"`
	IP         string `json:"ip"`
	Location   string `json:"location"`
	Status     int    `json:"status"`     // 0-失败, 1-成功
	FailReason string `json:"fail_reason"`
}

// ListLoginLogRequest 登录日志列表请求
type ListLoginLogRequest struct {
	UserID     int64  `json:"user_id" form:"user_id"`
	Status     *int   `json:"status" form:"status"` // 0-失败, 1-成功, nil-不筛选
	Page       int    `json:"page" form:"page"`
	PageSize   int    `json:"page_size" form:"page_size"`
	StartAt    string `json:"start_at" form:"start_at"`
	EndAt      string `json:"end_at" form:"end_at"`
	// Cursor-based pagination (preferred over Page/PageSize)
	Cursor string `form:"cursor"` // Opaque cursor from previous response
	Size   int    `form:"size"`    // Page size when using cursor mode
}

// GetLoginLogs 获取登录日志列表
func (s *LoginLogService) GetLoginLogs(ctx context.Context, req *ListLoginLogRequest) ([]*domain.LoginLog, int64, error) {
	if req.Page <= 0 {
		req.Page = 1
	}
	if req.PageSize <= 0 {
		req.PageSize = 20
	}
	offset := (req.Page - 1) * req.PageSize

	// 按用户 ID 查询
	if req.UserID > 0 {
		return s.loginLogRepo.ListByUserID(ctx, req.UserID, offset, req.PageSize)
	}

	// 按时间范围查询
	if req.StartAt != "" && req.EndAt != "" {
		start, err1 := time.Parse(time.RFC3339, req.StartAt)
		end, err2 := time.Parse(time.RFC3339, req.EndAt)
		if err1 == nil && err2 == nil {
			return s.loginLogRepo.ListByTimeRange(ctx, start, end, offset, req.PageSize)
		}
	}

	// 按状态查询（仅当明确指定了状态时才筛选）
	if req.Status != nil && (*req.Status == 0 || *req.Status == 1) {
		return s.loginLogRepo.ListByStatus(ctx, *req.Status, offset, req.PageSize)
	}

	return s.loginLogRepo.List(ctx, offset, req.PageSize)
}

// CursorResult wraps cursor-based pagination response
type CursorResult struct {
	Items      interface{} `json:"items"`
	NextCursor string      `json:"next_cursor"`
	HasMore    bool        `json:"has_more"`
	PageSize   int         `json:"page_size"`
}

// GetLoginLogsCursor 游标分页获取登录日志列表（推荐使用）
func (s *LoginLogService) GetLoginLogsCursor(ctx context.Context, req *ListLoginLogRequest) (*CursorResult, error) {
	size := pagination.ClampPageSize(req.Size)
	if req.PageSize > 0 && req.Cursor == "" {
		size = pagination.ClampPageSize(req.PageSize)
	}

	cursor, err := pagination.Decode(req.Cursor)
	if err != nil {
		return nil, fmt.Errorf("invalid cursor: %w", err)
	}

	var items interface{}
	var nextCursor string
	var hasMore bool

	// 按用户 ID 查询
	if req.UserID > 0 {
		logs, hm, err := s.loginLogRepo.ListByUserIDCursor(ctx, req.UserID, size, cursor)
		if err != nil {
			return nil, err
		}
		items = logs
		hasMore = hm
	} else if req.StartAt != "" && req.EndAt != "" {
		// Time range: fall back to offset-based for now (cursor + time range is complex)
		start, err1 := time.Parse(time.RFC3339, req.StartAt)
		end, err2 := time.Parse(time.RFC3339, req.EndAt)
		if err1 == nil && err2 == nil {
			offset := 0
			logs, _, err := s.loginLogRepo.ListByTimeRange(ctx, start, end, offset, size)
			if err != nil {
				return nil, err
			}
			items = logs
			if len(logs) > 0 {
				last := logs[len(logs)-1]
				nextCursor = pagination.BuildNextCursor(last.ID, last.CreatedAt)
				hasMore = len(logs) == size
			}
		} else {
			items = []*domain.LoginLog{}
		}
	} else if req.Status != nil && (*req.Status == 0 || *req.Status == 1) {
		// Status filter: use ListCursor with manual status filter
		logs, hm, err := s.listByStatusCursor(ctx, *req.Status, size, cursor)
		if err != nil {
			return nil, err
		}
		items = logs
		hasMore = hm
	} else {
		// Default: full table cursor scan
		logs, hm, err := s.loginLogRepo.ListCursor(ctx, size, cursor)
		if err != nil {
			return nil, err
		}
		items = logs
		hasMore = hm
	}

	// Build next cursor from the last item
	if nextCursor == "" {
		switch items := items.(type) {
		case []*domain.LoginLog:
			if len(items) > 0 {
				last := items[len(items)-1]
				nextCursor = pagination.BuildNextCursor(last.ID, last.CreatedAt)
			}
		}
	}

	return &CursorResult{
		Items:      items,
		NextCursor: nextCursor,
		HasMore:    hasMore,
		PageSize:   size,
	}, nil
}

// listByStatusCursor 游标分页按状态查询（内部方法）
// Uses iterative approach: fetch from ListCursor and post-filter by status.
func (s *LoginLogService) listByStatusCursor(ctx context.Context, status int, limit int, cursor *pagination.Cursor) ([]*domain.LoginLog, bool, error) {
	var logs []*domain.LoginLog

	// Since LoginLogRepository doesn't have status+cursor combined,
	// we use a larger batch from ListCursor and post-filter.
	batchSize := limit + 1
	for attempts := 0; attempts < 10; attempts++ { // max 10 pages of skipping
		batch, hm, err := s.loginLogRepo.ListCursor(ctx, batchSize, cursor)
		if err != nil {
			return nil, false, err
		}
		for _, log := range batch {
			if log.Status == status {
				logs = append(logs, log)
				if len(logs) >= limit+1 {
					break
				}
			}
		}
		if len(logs) >= limit+1 || !hm || len(batch) == 0 {
			break
		}
		// Advance cursor to end of this batch
		if len(batch) > 0 {
			last := batch[len(batch)-1]
			cursor = &pagination.Cursor{LastID: last.ID, LastValue: last.CreatedAt}
		}
	}

	hasMore := len(logs) > limit
	if hasMore {
		logs = logs[:limit]
	}
	return logs, hasMore, nil
}

// GetMyLoginLogs 获取当前用户的登录日志
func (s *LoginLogService) GetMyLoginLogs(ctx context.Context, userID int64, page, pageSize int) ([]*domain.LoginLog, int64, error) {
	if page <= 0 {
		page = 1
	}
	if pageSize <= 0 {
		pageSize = 20
	}
	offset := (page - 1) * pageSize
	return s.loginLogRepo.ListByUserID(ctx, userID, offset, pageSize)
}

// CleanupOldLogs 清理旧日志（保留最近 N 天）
func (s *LoginLogService) CleanupOldLogs(ctx context.Context, retentionDays int) error {
	return s.loginLogRepo.DeleteOlderThan(ctx, retentionDays)
}

// ExportLoginLogRequest 导出登录日志请求
type ExportLoginLogRequest struct {
	UserID  int64  `form:"user_id"`
	Status  int    `form:"status"`
	Format  string `form:"format"`
	StartAt string `form:"start_at"`
	EndAt   string `form:"end_at"`
}

// ExportLoginLogs 导出登录日志
func (s *LoginLogService) ExportLoginLogs(ctx context.Context, req *ExportLoginLogRequest) ([]byte, string, string, error) {
	format := "csv"
	if req.Format == "xlsx" {
		format = "xlsx"
	}

	var startAt, endAt *time.Time
	if req.StartAt != "" {
		if t, err := time.Parse(time.RFC3339, req.StartAt); err == nil {
			startAt = &t
		}
	}
	if req.EndAt != "" {
		if t, err := time.Parse(time.RFC3339, req.EndAt); err == nil {
			endAt = &t
		}
	}

	// CSV 使用流式分批导出，XLSX 使用全量导出（excelize 需要所有行）
	if format == "csv" {
		data, filename, err := s.exportLoginLogsCSVStream(ctx, req.UserID, req.Status, startAt, endAt)
		if err != nil {
			return nil, "", "", err
		}
		return data, filename, "text/csv; charset=utf-8", nil
	}

	logs, err := s.loginLogRepo.ListAllForExport(ctx, req.UserID, req.Status, startAt, endAt)
	if err != nil {
		return nil, "", "", fmt.Errorf("查询登录日志失败: %w", err)
	}

	filename := fmt.Sprintf("login_logs_%s.xlsx", time.Now().Format("20060102_150405"))
	data, err := buildLoginLogXLSXExport(logs)
	if err != nil {
		return nil, "", "", err
	}
	return data, filename, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", nil
}

// exportLoginLogsCSVStream 流式导出 CSV（分批处理防止 OOM）
func (s *LoginLogService) exportLoginLogsCSVStream(ctx context.Context, userID int64, status int, startAt, endAt *time.Time) ([]byte, string, error) {
	headers := []string{"ID", "用户ID", "登录方式", "设备ID", "IP地址", "位置", "状态", "失败原因", "时间"}

	var buf bytes.Buffer
	buf.Write([]byte{0xEF, 0xBB, 0xBF})
	writer := csv.NewWriter(&buf)

	// 写入表头
	if err := writer.Write(headers); err != nil {
		return nil, "", fmt.Errorf("写CSV表头失败: %w", err)
	}

	// 使用游标分批获取数据
	cursor := int64(1<<63 - 1) // 从最大 ID 开始
	batchSize := 5000
	totalWritten := 0

	for {
		logs, hasMore, err := s.loginLogRepo.ListLogsForExportBatch(ctx, userID, status, startAt, endAt, cursor, batchSize)
		if err != nil {
			return nil, "", fmt.Errorf("查询登录日志失败: %w", err)
		}

		for _, log := range logs {
			row := []string{
				fmt.Sprintf("%d", log.ID),
				fmt.Sprintf("%d", derefInt64(log.UserID)),
				loginTypeLabel(log.LoginType),
				log.DeviceID,
				log.IP,
				log.Location,
				loginStatusLabel(log.Status),
				log.FailReason,
				log.CreatedAt.Format("2006-01-02 15:04:05"),
			}
			if err := writer.Write(row); err != nil {
				return nil, "", fmt.Errorf("写CSV行失败: %w", err)
			}
			totalWritten++
			cursor = log.ID
		}

		writer.Flush()
		if err := writer.Error(); err != nil {
			return nil, "", fmt.Errorf("CSV Flush 失败: %w", err)
		}

		// 如果数据量过大，提前终止
		if totalWritten >= repository.ExportBatchSize {
			break
		}

		if !hasMore || len(logs) == 0 {
			break
		}
	}

	filename := fmt.Sprintf("login_logs_%s.csv", time.Now().Format("20060102_150405"))
	return buf.Bytes(), filename, nil
}

func buildLoginLogCSVExport(logs []*domain.LoginLog) ([]byte, error) {
	headers := []string{"ID", "用户ID", "登录方式", "设备ID", "IP地址", "位置", "状态", "失败原因", "时间"}
	rows := make([][]string, 0, len(logs)+1)
	rows = append(rows, headers)

	for _, log := range logs {
		rows = append(rows, []string{
			fmt.Sprintf("%d", log.ID),
			fmt.Sprintf("%d", derefInt64(log.UserID)),
			loginTypeLabel(log.LoginType),
			log.DeviceID,
			log.IP,
			log.Location,
			loginStatusLabel(log.Status),
			log.FailReason,
			log.CreatedAt.Format("2006-01-02 15:04:05"),
		})
	}

	var buf bytes.Buffer
	buf.Write([]byte{0xEF, 0xBB, 0xBF})
	writer := csv.NewWriter(&buf)
	if err := writer.WriteAll(rows); err != nil {
		return nil, fmt.Errorf("写CSV失败: %w", err)
	}
	return buf.Bytes(), nil
}

func buildLoginLogXLSXExport(logs []*domain.LoginLog) ([]byte, error) {
	file := excelize.NewFile()
	defer file.Close()

	sheet := file.GetSheetName(file.GetActiveSheetIndex())
	if sheet == "" {
		sheet = "Sheet1"
	}

	headers := []string{"ID", "用户ID", "登录方式", "设备ID", "IP地址", "位置", "状态", "失败原因", "时间"}
	for idx, header := range headers {
		cell, _ := excelize.CoordinatesToCellName(idx+1, 1)
		_ = file.SetCellValue(sheet, cell, header)
	}

	for rowIdx, log := range logs {
		row := []string{
			fmt.Sprintf("%d", log.ID),
			fmt.Sprintf("%d", derefInt64(log.UserID)),
			loginTypeLabel(log.LoginType),
			log.DeviceID,
			log.IP,
			log.Location,
			loginStatusLabel(log.Status),
			log.FailReason,
			log.CreatedAt.Format("2006-01-02 15:04:05"),
		}
		for colIdx, value := range row {
			cell, _ := excelize.CoordinatesToCellName(colIdx+1, rowIdx+2)
			_ = file.SetCellValue(sheet, cell, value)
		}
	}

	var buf bytes.Buffer
	if _, err := file.WriteTo(&buf); err != nil {
		return nil, fmt.Errorf("生成Excel失败: %w", err)
	}
	return buf.Bytes(), nil
}

func loginTypeLabel(t int) string {
	switch t {
	case 1:
		return "密码登录"
	case 2:
		return "邮箱验证码"
	case 3:
		return "手机验证码"
	case 4:
		return "OAuth"
	default:
		return "未知"
	}
}

func loginStatusLabel(s int) string {
	if s == 1 {
		return "成功"
	}
	return "失败"
}

func derefInt64(v *int64) int64 {
	if v == nil {
		return 0
	}
	return *v
}
