package mail

import (
	"context"
	"errors"
	"strings"

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

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

var (
	ErrFolderProtected  = errors.New("system folder cannot be deleted")
	ErrDuplicateFolder  = errors.New("duplicate folder remote_name")
	ErrDuplicateLabel   = errors.New("duplicate label name")
)

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

func scanFolderRow(id, accountID, name, remoteName, folderType string, uidvalidity int64, messageCount, unreadCount int, createdAt, updatedAt any) map[string]any {
	return map[string]any{
		"id":             id,
		"account_id":     accountID,
		"name":           name,
		"remote_name":    remoteName,
		"folder_type":    folderType,
		"uidvalidity":    uidvalidity,
		"message_count":  messageCount,
		"unread_count":   unreadCount,
		"created_at":     createdAt,
		"updated_at":     updatedAt,
	}
}

func (s *Service) ListFolders(ctx context.Context, externalID, accountID string, params query.ListParams) (FoldersList, error) {
	var owned bool
	if err := s.db.QueryRow(ctx, `
		SELECT EXISTS(
			SELECT 1 FROM mail_accounts
			WHERE id = $1 AND user_id = (SELECT id FROM users WHERE external_id = $2)
		)
	`, accountID, externalID).Scan(&owned); err != nil {
		return FoldersList{}, err
	}
	if !owned {
		return FoldersList{}, ErrAccountNotFound
	}

	var total int64
	if err := s.db.QueryRow(ctx, `
		SELECT COUNT(*) FROM mail_folders WHERE account_id = $1
	`, accountID).Scan(&total); err != nil {
		return FoldersList{}, err
	}

	rows, err := s.db.Query(ctx, `
		SELECT id, account_id, name, remote_name, folder_type, uidvalidity, message_count, unread_count, created_at, updated_at
		FROM mail_folders
		WHERE account_id = $1
		ORDER BY name ASC
		LIMIT $2 OFFSET $3
	`, accountID, params.Limit(), params.Offset())
	if err != nil {
		return FoldersList{}, err
	}
	defer rows.Close()

	folders := make([]map[string]any, 0)
	for rows.Next() {
		var id, acctID, name, remoteName, folderType string
		var uidvalidity int64
		var messageCount, unreadCount int
		var createdAt, updatedAt any
		if err := rows.Scan(&id, &acctID, &name, &remoteName, &folderType, &uidvalidity, &messageCount, &unreadCount, &createdAt, &updatedAt); err != nil {
			return FoldersList{}, err
		}
		folders = append(folders, scanFolderRow(id, acctID, name, remoteName, folderType, uidvalidity, messageCount, unreadCount, createdAt, updatedAt))
	}
	if err := rows.Err(); err != nil {
		return FoldersList{}, err
	}

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

func (s *Service) GetFolder(ctx context.Context, externalID, folderID string) (map[string]any, error) {
	var id, accountID, name, remoteName, folderType string
	var uidvalidity int64
	var messageCount, unreadCount int
	var createdAt, updatedAt any
	err := s.db.QueryRow(ctx, `
		SELECT f.id, f.account_id, f.name, f.remote_name, f.folder_type, f.uidvalidity, f.message_count, f.unread_count, f.created_at, f.updated_at
		FROM mail_folders f
		JOIN mail_accounts ma ON f.account_id = ma.id
		WHERE f.id = $1 AND ma.user_id = (SELECT id FROM users WHERE external_id = $2)
	`, folderID, externalID).Scan(&id, &accountID, &name, &remoteName, &folderType, &uidvalidity, &messageCount, &unreadCount, &createdAt, &updatedAt)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, ErrNotFound
		}
		return nil, err
	}
	return scanFolderRow(id, accountID, name, remoteName, folderType, uidvalidity, messageCount, unreadCount, createdAt, updatedAt), nil
}

func (s *Service) CreateFolder(ctx context.Context, userID string, req *createFolderRequest) (string, error) {
	var owned bool
	if err := s.db.QueryRow(ctx, `
		SELECT EXISTS(SELECT 1 FROM mail_accounts WHERE id = $1 AND user_id = $2)
	`, req.AccountID, userID).Scan(&owned); err != nil {
		return "", err
	}
	if !owned {
		return "", ErrAccountNotFound
	}

	remoteName := strings.TrimSpace(req.RemoteName)
	if remoteName == "" {
		remoteName = strings.TrimSpace(req.Name)
	}
	folderType := normalizeFolderType(req.FolderType)

	var id string
	err := s.db.QueryRow(ctx, `
		INSERT INTO mail_folders (account_id, name, remote_name, folder_type)
		VALUES ($1, $2, $3, $4)
		RETURNING id
	`, req.AccountID, strings.TrimSpace(req.Name), remoteName, folderType).Scan(&id)
	if err != nil {
		if isUniqueViolation(err) {
			return "", ErrDuplicateFolder
		}
		return "", err
	}
	return id, nil
}

func (s *Service) UpdateFolder(ctx context.Context, externalID, folderID string, req *updateFolderRequest) error {
	folderType := normalizeFolderType(req.FolderType)
	result, err := s.db.Exec(ctx, `
		UPDATE mail_folders f SET
			name = $1,
			remote_name = $2,
			folder_type = $3,
			updated_at = NOW()
		FROM mail_accounts ma
		WHERE f.id = $4
		  AND f.account_id = ma.id
		  AND ma.user_id = (SELECT id FROM users WHERE external_id = $5)
	`, strings.TrimSpace(req.Name), strings.TrimSpace(req.RemoteName), folderType, folderID, externalID)
	if err != nil {
		if isUniqueViolation(err) {
			return ErrDuplicateFolder
		}
		return err
	}
	if result.RowsAffected() == 0 {
		return ErrNotFound
	}
	return nil
}

func (s *Service) DeleteFolder(ctx context.Context, externalID, folderID string) error {
	var folderType string
	err := s.db.QueryRow(ctx, `
		SELECT f.folder_type
		FROM mail_folders f
		JOIN mail_accounts ma ON f.account_id = ma.id
		WHERE f.id = $1 AND ma.user_id = (SELECT id FROM users WHERE external_id = $2)
	`, folderID, externalID).Scan(&folderType)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return ErrNotFound
		}
		return err
	}
	if folderType != "custom" {
		return ErrFolderProtected
	}

	result, err := s.db.Exec(ctx, `
		DELETE FROM mail_folders f
		USING mail_accounts ma
		WHERE f.id = $1
		  AND f.account_id = ma.id
		  AND ma.user_id = (SELECT id FROM users WHERE external_id = $2)
	`, folderID, externalID)
	if err != nil {
		return err
	}
	if result.RowsAffected() == 0 {
		return ErrNotFound
	}
	if s.audit != nil {
		s.audit.Log(ctx, externalID, securityaudit.ActionCriticalDeletion, map[string]any{
			"target": "mail_folder", "folder_id": folderID,
		})
	}
	return nil
}

func isUniqueViolation(err error) bool {
	var pgErr *pgconn.PgError
	return errors.As(err, &pgErr) && pgErr.Code == "23505"
}
