package message

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

type MessageTopNoticeState struct {
	ScopeID                 string
	ViewerMemberID          string
	TopNotice               *MessageTopNotice
	TopNoticeRevision       string
	ViewerTopNoticeRevision string
}

type MessageTopNoticeMutationResult struct {
	Changed     bool
	MessageID   string
	TopNoticeID string
	TopNotice   *MessageTopNotice
	State       MessageTopNoticeState
}

type MessageTopNotice struct {
	ID                 string
	WorkspaceID        string
	ScopeID            string
	MessageID          string
	SetByMemberID      string
	SetBy              AuthorProfile
	SetAt              time.Time
	Message            AppMessage
	ViewerState        MessageTopNoticeViewerState
	ViewerCapabilities MessageTopNoticeViewerCapabilities
}

type MessageTopNoticeViewerState struct {
	HiddenFromBannerForMe bool
}

type MessageTopNoticeViewerCapabilities struct {
	CanJump                bool
	CanRemoveForEveryone   bool
	CanHideFromBannerForMe bool
	CanShowInBannerAgain   bool
}

type ScopeMessageTopNoticeRecord struct {
	ID            string
	WorkspaceID   string
	ScopeID       string
	MessageID     string
	SetByMemberID string
	SetAt         time.Time
	CreatedAt     time.Time
	UpdatedAt     time.Time
}

type ScopeMessageTopNoticeHideRecord struct {
	ID               string
	WorkspaceID      string
	ScopeID          string
	TopNoticeID      string
	MessageID        string
	HiddenByMemberID string
	HiddenAt         time.Time
	CreatedAt        time.Time
	UpdatedAt        time.Time
}

type ScopeMessageTopNoticeRow struct {
	TopNotice ScopeMessageTopNoticeRecord
	Message   ScopeMessageRecord
	Hide      *ScopeMessageTopNoticeHideRecord
	HasHide   bool
}

type ScopeMessageTopNoticeMutationResult struct {
	Changed   bool
	TopNotice ScopeMessageTopNoticeRecord
}

type ScopeMessageTopNoticeHideMutationResult struct {
	Changed bool
	Hide    ScopeMessageTopNoticeHideRecord
}

func (store *Store) GetMessageTopNoticeForUser(ctx context.Context, userID, workspaceID, scopeID string) (MessageTopNoticeState, error) {
	var result MessageTopNoticeState
	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		var err error
		result, err = store.InTx(tx).GetMessageTopNoticeForUser(ctx, userID, workspaceID, scopeID)
		return err
	})
	if err != nil {
		return MessageTopNoticeState{}, err
	}
	return result, nil
}

func (store *Store) TopMessageForUser(ctx context.Context, userID, workspaceID, scopeID, messageID string) (MessageTopNoticeMutationResult, error) {
	var result MessageTopNoticeMutationResult
	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		var err error
		result, err = store.InTx(tx).TopMessageForUser(ctx, userID, workspaceID, scopeID, messageID)
		return err
	})
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	return result, nil
}

func (store *Store) UntopMessageForEveryoneForUser(ctx context.Context, userID, workspaceID, scopeID, messageID string) (MessageTopNoticeMutationResult, error) {
	var result MessageTopNoticeMutationResult
	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		var err error
		result, err = store.InTx(tx).UntopMessageForEveryoneForUser(ctx, userID, workspaceID, scopeID, messageID)
		return err
	})
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	return result, nil
}

func (store *Store) HideMessageTopNoticeBannerForUser(ctx context.Context, userID, workspaceID, scopeID, messageID, topNoticeID string) (MessageTopNoticeMutationResult, error) {
	var result MessageTopNoticeMutationResult
	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		var err error
		result, err = store.InTx(tx).HideMessageTopNoticeBannerForUser(ctx, userID, workspaceID, scopeID, messageID, topNoticeID)
		return err
	})
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	return result, nil
}

func (store *Store) ShowMessageTopNoticeBannerForUser(ctx context.Context, userID, workspaceID, scopeID, messageID, topNoticeID string) (MessageTopNoticeMutationResult, error) {
	var result MessageTopNoticeMutationResult
	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		var err error
		result, err = store.InTx(tx).ShowMessageTopNoticeBannerForUser(ctx, userID, workspaceID, scopeID, messageID, topNoticeID)
		return err
	})
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	return result, nil
}

func (store *Store) ResetMessageTopNoticeHidesForUser(ctx context.Context, userID, workspaceID, scopeID string) (MessageTopNoticeMutationResult, error) {
	var result MessageTopNoticeMutationResult
	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		var err error
		result, err = store.InTx(tx).ResetMessageTopNoticeHidesForUser(ctx, userID, workspaceID, scopeID)
		return err
	})
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	return result, nil
}

func (txs TxStore) GetMessageTopNoticeForUser(ctx context.Context, userID, workspaceID, scopeID string) (MessageTopNoticeState, error) {
	target, err := txs.resolveMessagePinTargetForUser(ctx, userID, workspaceID, scopeID, false)
	if err != nil {
		return MessageTopNoticeState{}, err
	}
	return txs.messageTopNoticeStateForTarget(ctx, target)
}

func (txs TxStore) TopMessageForUser(ctx context.Context, userID, workspaceID, scopeID, messageID string) (MessageTopNoticeMutationResult, error) {
	target, err := txs.resolveMessagePinTargetForUser(ctx, userID, workspaceID, scopeID, true)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	message, err := txs.loadMessageForPinMutation(ctx, target.WorkspaceID, target.ScopeID, messageID, true)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	if err := txs.requirePinMessageVisible(ctx, target, message.ID); err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	now := time.Now().UTC()
	topNoticeID, err := identifiers.NewEntityID("topnotice")
	if err != nil {
		return MessageTopNoticeMutationResult{}, fmt.Errorf("generate message top notice id: %w", err)
	}
	mutation, err := NewScopeRepositories(txs.tx).Pins.UpsertActiveTopNotice(ctx, ScopeMessageTopNoticeRecord{
		ID:            topNoticeID,
		WorkspaceID:   target.WorkspaceID,
		ScopeID:       target.ScopeID,
		MessageID:     message.ID,
		SetByMemberID: target.MemberID,
		SetAt:         now,
		CreatedAt:     now,
		UpdatedAt:     now,
	})
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	state, err := txs.messageTopNoticeStateForTarget(ctx, target)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	return MessageTopNoticeMutationResult{Changed: mutation.Changed, MessageID: message.ID, TopNoticeID: mutation.TopNotice.ID, TopNotice: state.TopNotice, State: state}, nil
}

func (txs TxStore) UntopMessageForEveryoneForUser(ctx context.Context, userID, workspaceID, scopeID, messageID string) (MessageTopNoticeMutationResult, error) {
	target, err := txs.resolveMessagePinTargetForUser(ctx, userID, workspaceID, scopeID, true)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	message, err := txs.loadMessageForPinMutation(ctx, target.WorkspaceID, target.ScopeID, messageID, true)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	if err := txs.requirePinMessageVisible(ctx, target, message.ID); err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	mutation, err := NewScopeRepositories(txs.tx).Pins.DeleteActiveTopNotice(ctx, target.WorkspaceID, target.ScopeID, message.ID)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	state, err := txs.messageTopNoticeStateForTarget(ctx, target)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	return MessageTopNoticeMutationResult{Changed: mutation.Changed, MessageID: message.ID, TopNoticeID: mutation.TopNotice.ID, State: state}, nil
}

func (txs TxStore) HideMessageTopNoticeBannerForUser(ctx context.Context, userID, workspaceID, scopeID, messageID, topNoticeID string) (MessageTopNoticeMutationResult, error) {
	target, err := txs.resolveMessagePinTargetForUser(ctx, userID, workspaceID, scopeID, false)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	message, err := txs.loadMessageForPinMutation(ctx, target.WorkspaceID, target.ScopeID, messageID, false)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	if err := txs.requirePinMessageVisible(ctx, target, message.ID); err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	topNotice, found, err := NewScopeRepositories(txs.tx).Pins.LoadActiveTopNotice(ctx, target.WorkspaceID, target.ScopeID, message.ID)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	if !found || !topNoticePreconditionMatches(topNotice, topNoticeID) {
		state, stateErr := txs.messageTopNoticeStateForTarget(ctx, target)
		if stateErr != nil {
			return MessageTopNoticeMutationResult{}, stateErr
		}
		return MessageTopNoticeMutationResult{Changed: false, MessageID: message.ID, TopNoticeID: strings.TrimSpace(topNoticeID), State: state}, nil
	}
	now := time.Now().UTC()
	hideID, err := identifiers.NewEntityID("noticehide")
	if err != nil {
		return MessageTopNoticeMutationResult{}, fmt.Errorf("generate message top notice banner hide id: %w", err)
	}
	mutation, err := NewScopeRepositories(txs.tx).Pins.InsertTopNoticeHide(ctx, ScopeMessageTopNoticeHideRecord{
		ID:               hideID,
		WorkspaceID:      target.WorkspaceID,
		ScopeID:          target.ScopeID,
		TopNoticeID:      topNotice.ID,
		MessageID:        message.ID,
		HiddenByMemberID: target.MemberID,
		HiddenAt:         now,
		CreatedAt:        now,
		UpdatedAt:        now,
	})
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	state, err := txs.messageTopNoticeStateForTarget(ctx, target)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	return MessageTopNoticeMutationResult{Changed: mutation.Changed, MessageID: message.ID, TopNoticeID: topNotice.ID, TopNotice: state.TopNotice, State: state}, nil
}

func (txs TxStore) ShowMessageTopNoticeBannerForUser(ctx context.Context, userID, workspaceID, scopeID, messageID, topNoticeID string) (MessageTopNoticeMutationResult, error) {
	target, err := txs.resolveMessagePinTargetForUser(ctx, userID, workspaceID, scopeID, false)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	message, err := txs.loadMessageForPinMutation(ctx, target.WorkspaceID, target.ScopeID, messageID, false)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	if err := txs.requirePinMessageVisible(ctx, target, message.ID); err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	topNotice, found, err := NewScopeRepositories(txs.tx).Pins.LoadActiveTopNotice(ctx, target.WorkspaceID, target.ScopeID, message.ID)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	if !found || !topNoticePreconditionMatches(topNotice, topNoticeID) {
		state, stateErr := txs.messageTopNoticeStateForTarget(ctx, target)
		if stateErr != nil {
			return MessageTopNoticeMutationResult{}, stateErr
		}
		return MessageTopNoticeMutationResult{Changed: false, MessageID: message.ID, TopNoticeID: strings.TrimSpace(topNoticeID), State: state}, nil
	}
	mutation, err := NewScopeRepositories(txs.tx).Pins.DeleteTopNoticeHide(ctx, target.WorkspaceID, target.ScopeID, message.ID, topNotice.ID, target.MemberID)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	state, err := txs.messageTopNoticeStateForTarget(ctx, target)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	return MessageTopNoticeMutationResult{Changed: mutation.Changed, MessageID: message.ID, TopNoticeID: topNotice.ID, TopNotice: state.TopNotice, State: state}, nil
}

func (txs TxStore) ResetMessageTopNoticeHidesForUser(ctx context.Context, userID, workspaceID, scopeID string) (MessageTopNoticeMutationResult, error) {
	target, err := txs.resolveMessagePinTargetForUser(ctx, userID, workspaceID, scopeID, false)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	deleted, err := NewScopeRepositories(txs.tx).Pins.DeleteTopNoticeHidesForViewerScope(ctx, target.WorkspaceID, target.ScopeID, target.MemberID)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	state, err := txs.messageTopNoticeStateForTarget(ctx, target)
	if err != nil {
		return MessageTopNoticeMutationResult{}, err
	}
	return MessageTopNoticeMutationResult{Changed: deleted > 0, State: state}, nil
}

func (txs TxStore) messageTopNoticeStateForTarget(ctx context.Context, target messagePinTarget) (MessageTopNoticeState, error) {
	canWrite, err := txs.canWriteMessagePinTarget(ctx, target.UserID, target.WorkspaceID, target.ScopeID)
	if err != nil {
		return MessageTopNoticeState{}, err
	}
	row, found, err := NewScopeRepositories(txs.tx).Pins.GetActiveTopNotice(ctx, target.WorkspaceID, target.ScopeID, target.MemberID)
	if err != nil {
		return MessageTopNoticeState{}, err
	}
	state := MessageTopNoticeState{ScopeID: target.ScopeID, ViewerMemberID: target.MemberID}
	if !found {
		return state, nil
	}
	visible, err := txs.visibleMessageTopNoticeRowForTarget(ctx, target, row)
	if err != nil {
		return MessageTopNoticeState{}, err
	}
	if visible == nil {
		return state, nil
	}
	topNotice, maxUpdatedAt, maxHiddenAt, err := txs.messageTopNoticeFromVisibleRow(ctx, target, *visible, canWrite)
	if err != nil {
		return MessageTopNoticeState{}, err
	}
	state.TopNotice = topNotice
	state.TopNoticeRevision = messagePinRevision("top_notice", maxUpdatedAt, 1)
	if topNotice != nil && topNotice.ViewerState.HiddenFromBannerForMe {
		state.ViewerTopNoticeRevision = messagePinRevision("top_notice_viewer", maxHiddenAt, 1)
	}
	return state, nil
}

func (txs TxStore) visibleMessageTopNoticeRowForTarget(ctx context.Context, target messagePinTarget, row ScopeMessageTopNoticeRow) (*ScopeMessageTopNoticeRow, error) {
	decision, err := NewScopeAPIServiceForQueryer(txs.tx).AuthorizeSidecarRead(ctx, SidecarReadRequest{
		WorkspaceID:    target.WorkspaceID,
		MessageID:      strings.TrimSpace(row.TopNotice.MessageID),
		ViewerMemberID: target.MemberID,
		ViewerType:     ScopeActorHuman,
		Kind:           SidecarKindPin,
	})
	if err != nil {
		return nil, err
	}
	if !decision.Allowed || strings.TrimSpace(decision.ScopeID) != target.ScopeID {
		return nil, nil
	}
	return &row, nil
}

func (txs TxStore) messageTopNoticeFromVisibleRow(ctx context.Context, target messagePinTarget, row ScopeMessageTopNoticeRow, canWrite bool) (*MessageTopNotice, time.Time, time.Time, error) {
	service := NewScopeAPIServiceForQueryer(txs.tx)
	messages, err := service.appMessagesFromVisibleRefs(ctx, target.WorkspaceID, []VisibleMessageRef{{Message: row.Message, Reason: ScopeVisibilityReasonMembership}})
	if err != nil {
		return nil, time.Time{}, time.Time{}, err
	}
	if err := AttachResolvedMentionsForAppMessages(ctx, service.db, target.WorkspaceID, target.MemberID, messages); err != nil {
		return nil, time.Time{}, time.Time{}, err
	}
	if err := AttachMessagePinStateForAppMessages(ctx, service.db, target.WorkspaceID, target.MemberID, messages); err != nil {
		return nil, time.Time{}, time.Time{}, err
	}
	if len(messages) == 0 {
		return nil, time.Time{}, time.Time{}, nil
	}
	setters, err := loadScopeMemberPresentations(ctx, txs.tx, target.WorkspaceID, []string{row.TopNotice.SetByMemberID})
	if err != nil {
		return nil, time.Time{}, time.Time{}, err
	}
	setter := setters[strings.TrimSpace(row.TopNotice.SetByMemberID)].AuthorProfile().Normalize()
	hidden := row.HasHide
	var maxHiddenAt time.Time
	if row.Hide != nil {
		maxHiddenAt = row.Hide.HiddenAt
	}
	return &MessageTopNotice{
		ID:            row.TopNotice.ID,
		WorkspaceID:   row.TopNotice.WorkspaceID,
		ScopeID:       row.TopNotice.ScopeID,
		MessageID:     row.TopNotice.MessageID,
		SetByMemberID: row.TopNotice.SetByMemberID,
		SetBy:         setter,
		SetAt:         row.TopNotice.SetAt,
		Message:       messages[0],
		ViewerState: MessageTopNoticeViewerState{
			HiddenFromBannerForMe: hidden,
		},
		ViewerCapabilities: MessageTopNoticeViewerCapabilities{
			CanJump:                true,
			CanRemoveForEveryone:   canWrite,
			CanHideFromBannerForMe: !hidden,
			CanShowInBannerAgain:   hidden,
		},
	}, row.TopNotice.UpdatedAt, maxHiddenAt, nil
}

func topNoticePreconditionMatches(topNotice ScopeMessageTopNoticeRecord, topNoticeID string) bool {
	topNoticeID = strings.TrimSpace(topNoticeID)
	return topNoticeID == "" || strings.TrimSpace(topNotice.ID) == topNoticeID
}

func normalizeScopeMessageTopNoticeRecord(record ScopeMessageTopNoticeRecord) ScopeMessageTopNoticeRecord {
	now := time.Now().UTC()
	record.ID = strings.TrimSpace(record.ID)
	record.WorkspaceID = strings.TrimSpace(record.WorkspaceID)
	record.ScopeID = strings.TrimSpace(record.ScopeID)
	record.MessageID = strings.TrimSpace(record.MessageID)
	record.SetByMemberID = strings.TrimSpace(record.SetByMemberID)
	if record.SetAt.IsZero() {
		record.SetAt = now
	}
	record.SetAt = record.SetAt.UTC()
	if record.CreatedAt.IsZero() {
		record.CreatedAt = record.SetAt
	}
	record.CreatedAt = record.CreatedAt.UTC()
	if record.UpdatedAt.IsZero() {
		record.UpdatedAt = record.CreatedAt
	}
	record.UpdatedAt = record.UpdatedAt.UTC()
	return record
}

func normalizeScopeMessageTopNoticeHideRecord(record ScopeMessageTopNoticeHideRecord) ScopeMessageTopNoticeHideRecord {
	now := time.Now().UTC()
	record.ID = strings.TrimSpace(record.ID)
	record.WorkspaceID = strings.TrimSpace(record.WorkspaceID)
	record.ScopeID = strings.TrimSpace(record.ScopeID)
	record.TopNoticeID = strings.TrimSpace(record.TopNoticeID)
	record.MessageID = strings.TrimSpace(record.MessageID)
	record.HiddenByMemberID = strings.TrimSpace(record.HiddenByMemberID)
	if record.HiddenAt.IsZero() {
		record.HiddenAt = now
	}
	record.HiddenAt = record.HiddenAt.UTC()
	if record.CreatedAt.IsZero() {
		record.CreatedAt = record.HiddenAt
	}
	record.CreatedAt = record.CreatedAt.UTC()
	if record.UpdatedAt.IsZero() {
		record.UpdatedAt = record.CreatedAt
	}
	record.UpdatedAt = record.UpdatedAt.UTC()
	return record
}
