package mail

import (
	"context"
	"encoding/json"
	"errors"

	"github.com/jackc/pgx/v5"

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

type IdentitiesList struct {
	Identities []map[string]any     `json:"identities"`
	Pagination query.PaginationMeta `json:"pagination,omitempty"`
}

const identitySelectColumns = `
	mi.id, mi.account_id, mi.email, mi.name, mi.is_default, mi.signature_html, mi.default_signature_id, mi.reply_to_addrs, mi.created_at, mi.updated_at
`

func (s *Service) verifyAccountOwnership(ctx context.Context, externalID, accountID string) error {
	var exists bool
	err := s.db.QueryRow(ctx, `
		SELECT EXISTS(
			SELECT 1 FROM mail_accounts ma
			JOIN users u ON ma.user_id = u.id
			WHERE ma.id = $1 AND u.external_id = $2
		)
	`, accountID, externalID).Scan(&exists)
	if err != nil {
		return err
	}
	if !exists {
		return ErrAccountNotFound
	}
	return nil
}

func identityOwnershipJoin() string {
	return `
		FROM mail_identities mi
		JOIN mail_accounts ma ON mi.account_id = ma.id
		JOIN users u ON ma.user_id = u.id
	`
}

func scanIdentity(id, accountID, email, name, signatureHTML string, isDefault bool, defaultSignatureID *string, replyToJSON []byte, createdAt, updatedAt any) map[string]any {
	replyTo := parseReplyToAddrs(replyToJSON)
	out := map[string]any{
		"id":             id,
		"account_id":     accountID,
		"email":          email,
		"name":           name,
		"is_default":     isDefault,
		"signature_html": signatureHTML,
		"reply_to_addrs": replyTo,
		"created_at":     createdAt,
		"updated_at":     updatedAt,
	}
	if defaultSignatureID != nil && *defaultSignatureID != "" {
		out["default_signature_id"] = *defaultSignatureID
	}
	return out
}

func nullableUUID(id string) any {
	if id == "" {
		return nil
	}
	return id
}

func parseReplyToAddrs(raw []byte) []string {
	if len(raw) == 0 {
		return []string{}
	}
	var addrs []string
	if err := json.Unmarshal(raw, &addrs); err != nil || addrs == nil {
		return []string{}
	}
	return addrs
}

func (s *Service) clearDefaultIdentities(ctx context.Context, accountID string) error {
	_, err := s.db.Exec(ctx, `
		UPDATE mail_identities SET is_default = false, updated_at = NOW()
		WHERE account_id = $1 AND is_default = true
	`, accountID)
	return err
}

func (s *Service) ListIdentities(ctx context.Context, externalID, accountID string, params query.ListParams) (IdentitiesList, error) {
	if err := s.verifyAccountOwnership(ctx, externalID, accountID); err != nil {
		return IdentitiesList{}, err
	}

	var total int64
	countQuery := "SELECT COUNT(*) " + identityOwnershipJoin() + " WHERE mi.account_id = $1 AND u.external_id = $2"
	if err := s.db.QueryRow(ctx, countQuery, accountID, externalID).Scan(&total); err != nil {
		return IdentitiesList{}, err
	}

	listQuery := "SELECT " + identitySelectColumns + identityOwnershipJoin() +
		" WHERE mi.account_id = $1 AND u.external_id = $2 ORDER BY mi.is_default DESC, mi.created_at ASC LIMIT $3 OFFSET $4"

	rows, err := s.db.Query(ctx, listQuery, accountID, externalID, params.Limit(), params.Offset())
	if err != nil {
		return IdentitiesList{}, err
	}
	defer rows.Close()

	identities := make([]map[string]any, 0)
	for rows.Next() {
		var id, acctID, email, name, signatureHTML string
		var isDefault bool
		var defaultSignatureID *string
		var replyToJSON []byte
		var createdAt, updatedAt any
		if err := rows.Scan(&id, &acctID, &email, &name, &isDefault, &signatureHTML, &defaultSignatureID, &replyToJSON, &createdAt, &updatedAt); err != nil {
			return IdentitiesList{}, err
		}
		identities = append(identities, scanIdentity(id, acctID, email, name, signatureHTML, isDefault, defaultSignatureID, replyToJSON, createdAt, updatedAt))
	}
	if err := rows.Err(); err != nil {
		return IdentitiesList{}, err
	}

	return IdentitiesList{
		Identities: identities,
		Pagination: params.Meta(&total),
	}, nil
}

func (s *Service) GetIdentity(ctx context.Context, externalID, identityID string) (map[string]any, error) {
	query := "SELECT " + identitySelectColumns + identityOwnershipJoin() + " WHERE mi.id = $1 AND u.external_id = $2"

	var id, accountID, email, name, signatureHTML string
	var isDefault bool
	var defaultSignatureID *string
	var replyToJSON []byte
	var createdAt, updatedAt any
	err := s.db.QueryRow(ctx, query, identityID, externalID).Scan(
		&id, &accountID, &email, &name, &isDefault, &signatureHTML, &defaultSignatureID, &replyToJSON, &createdAt, &updatedAt,
	)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, ErrNotFound
		}
		return nil, err
	}
	return scanIdentity(id, accountID, email, name, signatureHTML, isDefault, defaultSignatureID, replyToJSON, createdAt, updatedAt), nil
}

func (s *Service) CreateIdentity(ctx context.Context, externalID, accountID string, req *createIdentityRequest) (string, error) {
	if err := s.verifyAccountOwnership(ctx, externalID, accountID); err != nil {
		return "", err
	}

	if req.IsDefault {
		if err := s.clearDefaultIdentities(ctx, accountID); err != nil {
			return "", err
		}
	}

	if req.DefaultSignatureID == "" {
		sigID, err := s.ensureIdentityDefaultSignature(ctx, externalID, req.Email)
		if err != nil {
			return "", err
		}
		req.DefaultSignatureID = sigID
	} else if err := s.verifySignatureOwnership(ctx, externalID, req.DefaultSignatureID); err != nil {
		return "", err
	}

	replyToJSON, _ := json.Marshal(req.ReplyToAddrs)
	defaultSigID := nullableUUID(req.DefaultSignatureID)

	var id string
	err := s.db.QueryRow(ctx, `
		INSERT INTO mail_identities (account_id, email, name, is_default, signature_html, default_signature_id, reply_to_addrs)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
		RETURNING id
	`, accountID, req.Email, req.Name, req.IsDefault, req.SignatureHTML, defaultSigID, replyToJSON).Scan(&id)
	if err != nil {
		return "", err
	}
	return id, nil
}

func (s *Service) UpdateIdentity(ctx context.Context, externalID, identityID string, req *updateIdentityRequest) error {
	identity, err := s.GetIdentity(ctx, externalID, identityID)
	if err != nil {
		return err
	}
	accountID, _ := identity["account_id"].(string)

	if req.IsDefault {
		if err := s.clearDefaultIdentities(ctx, accountID); err != nil {
			return err
		}
	}

	if req.DefaultSignatureID != "" {
		if err := s.verifySignatureOwnership(ctx, externalID, req.DefaultSignatureID); err != nil {
			return err
		}
	}

	replyToJSON, _ := json.Marshal(req.ReplyToAddrs)
	defaultSigID := nullableUUID(req.DefaultSignatureID)

	result, err := s.db.Exec(ctx, `
		UPDATE mail_identities mi SET
			email = $1, name = $2, is_default = $3, signature_html = $4,
			default_signature_id = $5, reply_to_addrs = $6, updated_at = NOW()
		FROM mail_accounts ma
		JOIN users u ON ma.user_id = u.id
		WHERE mi.id = $7 AND mi.account_id = ma.id AND u.external_id = $8
	`, req.Email, req.Name, req.IsDefault, req.SignatureHTML, defaultSigID, replyToJSON, identityID, externalID)
	if err != nil {
		return err
	}
	if result.RowsAffected() == 0 {
		return ErrNotFound
	}
	return nil
}

func (s *Service) DeleteIdentity(ctx context.Context, externalID, identityID string) error {
	result, err := s.db.Exec(ctx, `
		DELETE FROM mail_identities mi
		USING mail_accounts ma, users u
		WHERE mi.id = $1 AND mi.account_id = ma.id AND ma.user_id = u.id AND u.external_id = $2
	`, identityID, 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_identity", "identity_id": identityID,
		})
	}
	return nil
}
