package mail

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgconn"

	"github.com/ultisuite/ulti-backend/internal/api/query"
)

type UnifiedFoldersList struct {
	Folders    []map[string]any     `json:"folders"`
	Pagination query.PaginationMeta `json:"pagination,omitempty"`
}

func scanUnifiedFolderRow(
	id string,
	accountID *string,
	parentID *string,
	name, color string,
	sortOrder int,
	createdAt, updatedAt any,
) map[string]any {
	row := map[string]any{
		"id":          id,
		"name":        name,
		"color":       color,
		"sort_order":  sortOrder,
		"created_at":  createdAt,
		"updated_at":  updatedAt,
		"scope":       "global",
	}
	if accountID != nil && *accountID != "" {
		row["account_id"] = *accountID
		row["scope"] = "account"
	}
	if parentID != nil && *parentID != "" {
		row["parent_id"] = *parentID
	}
	return row
}

func (s *Service) ListUnifiedFolders(ctx context.Context, externalID, accountID string, params query.ListParams) (UnifiedFoldersList, error) {
	userID, err := s.ResolveUserID(ctx, externalID)
	if err != nil {
		return UnifiedFoldersList{}, err
	}

	countQuery := `
		SELECT COUNT(*) FROM mail_unified_folders
		WHERE user_id = $1
	`
	listQuery := `
		SELECT id, account_id, parent_id, name, color, sort_order, created_at, updated_at
		FROM mail_unified_folders
		WHERE user_id = $1
	`
	args := []any{userID}

	switch strings.TrimSpace(accountID) {
	case "", "all":
		// all scopes
	case "global":
		countQuery += ` AND account_id IS NULL`
		listQuery += ` AND account_id IS NULL`
	default:
		var owned bool
		if err := s.db.QueryRow(ctx, `
			SELECT EXISTS(
				SELECT 1 FROM mail_accounts WHERE id = $1 AND user_id = $2
			)
		`, accountID, userID).Scan(&owned); err != nil {
			return UnifiedFoldersList{}, err
		}
		if !owned {
			return UnifiedFoldersList{}, ErrAccountNotFound
		}
		countQuery += ` AND account_id = $2`
		listQuery += ` AND account_id = $2`
		args = append(args, accountID)
	}

	var total int64
	if err := s.db.QueryRow(ctx, countQuery, args...).Scan(&total); err != nil {
		return UnifiedFoldersList{}, err
	}

	listQuery += fmt.Sprintf(` ORDER BY sort_order ASC, name ASC LIMIT $%d OFFSET $%d`, len(args)+1, len(args)+2)
	args = append(args, params.Limit(), params.Offset())

	rows, err := s.db.Query(ctx, listQuery, args...)
	if err != nil {
		return UnifiedFoldersList{}, err
	}
	defer rows.Close()

	folders := make([]map[string]any, 0)
	for rows.Next() {
		var id, name, color string
		var accountIDVal, parentIDVal *string
		var sortOrder int
		var createdAt, updatedAt any
		if err := rows.Scan(&id, &accountIDVal, &parentIDVal, &name, &color, &sortOrder, &createdAt, &updatedAt); err != nil {
			return UnifiedFoldersList{}, err
		}
		folders = append(folders, scanUnifiedFolderRow(id, accountIDVal, parentIDVal, name, color, sortOrder, createdAt, updatedAt))
	}
	if err := rows.Err(); err != nil {
		return UnifiedFoldersList{}, err
	}

	return UnifiedFoldersList{
		Folders:    folders,
		Pagination: params.Meta(&total),
	}, nil
}

func (s *Service) CreateUnifiedFolder(ctx context.Context, userID string, req *createUnifiedFolderRequest) (string, error) {
	scopeAccountID := strings.TrimSpace(req.AccountID)
	parentID := strings.TrimSpace(req.ParentID)

	if scopeAccountID != "" {
		var owned bool
		if err := s.db.QueryRow(ctx, `
			SELECT EXISTS(SELECT 1 FROM mail_accounts WHERE id = $1 AND user_id = $2)
		`, scopeAccountID, userID).Scan(&owned); err != nil {
			return "", err
		}
		if !owned {
			return "", ErrAccountNotFound
		}
	}

	if parentID != "" {
		var parentAccountID *string
		if err := s.db.QueryRow(ctx, `
			SELECT account_id FROM mail_unified_folders
			WHERE id = $1 AND user_id = $2
		`, parentID, userID).Scan(&parentAccountID); err != nil {
			if errors.Is(err, pgx.ErrNoRows) {
				return "", ErrNotFound
			}
			return "", err
		}
		if scopeAccountID == "" {
			if parentAccountID != nil {
				return "", ErrInvalidFolderScope
			}
		} else if parentAccountID == nil || *parentAccountID != scopeAccountID {
			return "", ErrInvalidFolderScope
		}
	}

	var id string
	var accountArg any
	if scopeAccountID == "" {
		accountArg = nil
	} else {
		accountArg = scopeAccountID
	}
	var parentArg any
	if parentID == "" {
		parentArg = nil
	} else {
		parentArg = parentID
	}

	err := s.db.QueryRow(ctx, `
		INSERT INTO mail_unified_folders (user_id, account_id, parent_id, name, color, sort_order)
		VALUES ($1, $2, $3, $4, $5, $6)
		RETURNING id
	`, userID, accountArg, parentArg, strings.TrimSpace(req.Name), strings.TrimSpace(req.Color), req.SortOrder).Scan(&id)
	if err != nil {
		if isUniqueViolation(err) {
			return "", ErrDuplicateFolder
		}
		return "", err
	}
	return id, nil
}

func (s *Service) UpdateUnifiedFolder(ctx context.Context, externalID, folderID string, req *updateUnifiedFolderRequest) error {
	userID, err := s.ResolveUserID(ctx, externalID)
	if err != nil {
		return err
	}

	if req.ParentID != nil {
		parentID := strings.TrimSpace(*req.ParentID)
		if parentID != "" {
			var parentAccountID *string
			if err := s.db.QueryRow(ctx, `
				SELECT account_id FROM mail_unified_folders
				WHERE id = $1 AND user_id = $2
			`, parentID, userID).Scan(&parentAccountID); err != nil {
				if errors.Is(err, pgx.ErrNoRows) {
					return ErrNotFound
				}
				return err
			}
			var folderAccountID *string
			if err := s.db.QueryRow(ctx, `
				SELECT account_id FROM mail_unified_folders
				WHERE id = $1 AND user_id = $2
			`, folderID, userID).Scan(&folderAccountID); err != nil {
				if errors.Is(err, pgx.ErrNoRows) {
					return ErrNotFound
				}
				return err
			}
			if (folderAccountID == nil) != (parentAccountID == nil) {
				return ErrInvalidFolderScope
			}
			if folderAccountID != nil && parentAccountID != nil && *folderAccountID != *parentAccountID {
				return ErrInvalidFolderScope
			}
		}
	}

	var parentArg any
	if req.ParentID == nil {
		result, err := s.db.Exec(ctx, `
			UPDATE mail_unified_folders SET
				name = $1,
				color = $2,
				sort_order = $3,
				updated_at = NOW()
			WHERE id = $4 AND user_id = $5
		`, strings.TrimSpace(req.Name), strings.TrimSpace(req.Color), req.SortOrder, folderID, userID)
		if err != nil {
			return err
		}
		if result.RowsAffected() == 0 {
			return ErrNotFound
		}
		return nil
	}

	parentID := strings.TrimSpace(*req.ParentID)
	if parentID == "" {
		parentArg = nil
	} else {
		parentArg = parentID
	}

	result, err := s.db.Exec(ctx, `
		UPDATE mail_unified_folders SET
			name = $1,
			color = $2,
			sort_order = $3,
			parent_id = $4,
			updated_at = NOW()
		WHERE id = $5 AND user_id = $6
	`, strings.TrimSpace(req.Name), strings.TrimSpace(req.Color), req.SortOrder, parentArg, folderID, userID)
	if err != nil {
		return err
	}
	if result.RowsAffected() == 0 {
		return ErrNotFound
	}
	return nil
}

func (s *Service) DeleteUnifiedFolder(ctx context.Context, externalID, folderID string) error {
	result, err := s.db.Exec(ctx, `
		DELETE FROM mail_unified_folders
		WHERE id = $1 AND user_id = (SELECT id FROM users WHERE external_id = $2)
	`, folderID, externalID)
	if err != nil {
		var pgErr *pgconn.PgError
		if errors.As(err, &pgErr) && pgErr.Code == "23503" {
			return ErrFolderHasChildren
		}
		return err
	}
	if result.RowsAffected() == 0 {
		return ErrNotFound
	}
	return nil
}
