package projection

import (
	"context"

	"github.com/zitadel/zitadel/internal/domain"
	"github.com/zitadel/zitadel/internal/errors"
	"github.com/zitadel/zitadel/internal/eventstore"
	"github.com/zitadel/zitadel/internal/eventstore/handler"
	"github.com/zitadel/zitadel/internal/eventstore/handler/crdb"
	"github.com/zitadel/zitadel/internal/repository/deviceauth"
)

const (
	DeviceAuthProjectionTable = "projections.device_authorizations"

	DeviceAuthColumnID         = "id"
	DeviceAuthColumnClientID   = "client_id"
	DeviceAuthColumnDeviceCode = "device_code"
	DeviceAuthColumnUserCode   = "user_code"
	DeviceAuthColumnExpires    = "expires"
	DeviceAuthColumnScopes     = "scopes"
	DeviceAuthColumnState      = "state"
	DeviceAuthColumnSubject    = "subject"

	DeviceAuthColumnCreationDate = "creation_date"
	DeviceAuthColumnChangeDate   = "change_date"
	DeviceAuthColumnSequence     = "sequence"
	DeviceAuthColumnInstanceID   = "instance_id"
)

type deviceAuthProjection struct {
	crdb.StatementHandler
}

func newDeviceAuthProjection(ctx context.Context, config crdb.StatementHandlerConfig) *deviceAuthProjection {
	p := new(deviceAuthProjection)
	config.ProjectionName = DeviceAuthProjectionTable
	config.Reducers = p.reducers()
	config.InitCheck = crdb.NewTableCheck(
		crdb.NewTable([]*crdb.Column{
			crdb.NewColumn(DeviceAuthColumnID, crdb.ColumnTypeText),
			crdb.NewColumn(DeviceAuthColumnClientID, crdb.ColumnTypeText),
			crdb.NewColumn(DeviceAuthColumnDeviceCode, crdb.ColumnTypeText),
			crdb.NewColumn(DeviceAuthColumnUserCode, crdb.ColumnTypeText),
			crdb.NewColumn(DeviceAuthColumnExpires, crdb.ColumnTypeTimestamp),
			crdb.NewColumn(DeviceAuthColumnScopes, crdb.ColumnTypeTextArray),
			crdb.NewColumn(DeviceAuthColumnState, crdb.ColumnTypeEnum, crdb.Default(domain.DeviceAuthStateInitiated)),
			crdb.NewColumn(DeviceAuthColumnSubject, crdb.ColumnTypeText, crdb.Default("")),
			crdb.NewColumn(DeviceAuthColumnCreationDate, crdb.ColumnTypeTimestamp),
			crdb.NewColumn(DeviceAuthColumnChangeDate, crdb.ColumnTypeTimestamp),
			crdb.NewColumn(DeviceAuthColumnSequence, crdb.ColumnTypeInt64),
			crdb.NewColumn(DeviceAuthColumnInstanceID, crdb.ColumnTypeText),
		},
			crdb.NewPrimaryKey(DeviceAuthColumnInstanceID, DeviceAuthColumnID),
			crdb.WithIndex(crdb.NewIndex("user_code", []string{DeviceAuthColumnInstanceID, DeviceAuthColumnUserCode})),
			crdb.WithIndex(crdb.NewIndex("device_code", []string{DeviceAuthColumnInstanceID, DeviceAuthColumnClientID, DeviceAuthColumnDeviceCode})),
		),
	)

	p.StatementHandler = crdb.NewStatementHandler(ctx, config)
	return p
}

func (p *deviceAuthProjection) reducers() []handler.AggregateReducer {
	return []handler.AggregateReducer{
		{
			Aggregate: deviceauth.AggregateType,
			EventRedusers: []handler.EventReducer{
				{
					Event:  deviceauth.AddedEventType,
					Reduce: p.reduceAdded,
				},
				{
					Event:  deviceauth.ApprovedEventType,
					Reduce: p.reduceAppoved,
				},
				{
					Event:  deviceauth.CanceledEventType,
					Reduce: p.reduceCanceled,
				},
				{
					Event:  deviceauth.RemovedEventType,
					Reduce: p.reduceRemoved,
				},
			},
		},
	}
}

func (p *deviceAuthProjection) reduceAdded(event eventstore.Event) (*handler.Statement, error) {
	e, ok := event.(*deviceauth.AddedEvent)
	if !ok {
		return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-chu6O", "reduce.wrong.event.type %T != %s", event, deviceauth.AddedEventType)
	}
	return crdb.NewCreateStatement(
		e,
		[]handler.Column{
			handler.NewCol(DeviceAuthColumnID, e.Aggregate().ID),
			handler.NewCol(DeviceAuthColumnClientID, e.ClientID),
			handler.NewCol(DeviceAuthColumnDeviceCode, e.DeviceCode),
			handler.NewCol(DeviceAuthColumnUserCode, e.UserCode),
			handler.NewCol(DeviceAuthColumnExpires, e.Expires),
			handler.NewCol(DeviceAuthColumnScopes, e.Scopes),
			handler.NewCol(DeviceAuthColumnCreationDate, e.CreationDate()),
			handler.NewCol(DeviceAuthColumnChangeDate, e.CreationDate()),
			handler.NewCol(DeviceAuthColumnSequence, e.Sequence()),
			handler.NewCol(DeviceAuthColumnInstanceID, e.Aggregate().InstanceID),
		},
	), nil
}

func (p *deviceAuthProjection) reduceAppoved(event eventstore.Event) (*handler.Statement, error) {
	e, ok := event.(*deviceauth.ApprovedEvent)
	if !ok {
		return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-kei0A", "reduce.wrong.event.type %T != %s", event, deviceauth.ApprovedEventType)
	}
	return crdb.NewUpdateStatement(e,
		[]handler.Column{
			handler.NewCol(DeviceAuthColumnState, domain.DeviceAuthStateApproved),
			handler.NewCol(DeviceAuthColumnSubject, e.Subject),
			handler.NewCol(DeviceAuthColumnChangeDate, e.CreationDate()),
			handler.NewCol(DeviceAuthColumnSequence, e.Sequence()),
		},
		[]handler.Condition{
			handler.NewCond(DeviceAuthColumnInstanceID, e.Aggregate().InstanceID),
			handler.NewCond(DeviceAuthColumnID, e.Aggregate().ID),
		},
	), nil
}

func (p *deviceAuthProjection) reduceCanceled(event eventstore.Event) (*handler.Statement, error) {
	e, ok := event.(*deviceauth.CanceledEvent)
	if !ok {
		return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-eeS8d", "reduce.wrong.event.type %T != %s", event, deviceauth.CanceledEventType)
	}
	return crdb.NewUpdateStatement(e,
		[]handler.Column{
			handler.NewCol(DeviceAuthColumnState, e.Reason.State()),
			handler.NewCol(DeviceAuthColumnChangeDate, e.CreationDate()),
			handler.NewCol(DeviceAuthColumnSequence, e.Sequence()),
		},
		[]handler.Condition{
			handler.NewCond(DeviceAuthColumnInstanceID, e.Aggregate().InstanceID),
			handler.NewCond(DeviceAuthColumnID, e.Aggregate().ID),
		},
	), nil
}

func (p *deviceAuthProjection) reduceRemoved(event eventstore.Event) (*handler.Statement, error) {
	e, ok := event.(*deviceauth.RemovedEvent)
	if !ok {
		return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-AJi1u", "reduce.wrong.event.type %T != %s", event, deviceauth.RemovedEventType)
	}
	return crdb.NewDeleteStatement(e,
		[]handler.Condition{
			handler.NewCond(DeviceAuthColumnInstanceID, e.Aggregate().InstanceID),
			handler.NewCond(DeviceAuthColumnID, e.Aggregate().ID),
		},
	), nil
}