package migration

import (
	"bytes"
	"context"
	"fmt"

	"github.com/bluesky-social/indigo/api/agnostic"
	comatproto "github.com/bluesky-social/indigo/api/atproto"
	"github.com/bluesky-social/indigo/atproto/atclient"
	"github.com/bluesky-social/indigo/atproto/syntax"
	"github.com/bluesky-social/indigo/lex/util"
	"github.com/bluesky-social/indigo/xrpc"
	"github.com/ipfs/go-cid"
	"github.com/multiformats/go-multihash"
	"tangled.org/core/api/tangled"
	"tangled.org/core/appview/db"
	"tangled.org/core/appview/models"
	"tangled.org/core/orm"
)

func (s *Migration) migrateUseFeedComment(ctx context.Context, client *atclient.APIClient, did syntax.DID, record syntax.ATURI) error {
	l := s.logger.With("aturi", record)
	l.Debug("migrating record")

	switch record.Collection() {
	case tangled.RepoIssueCommentNSID:
	case tangled.RepoPullCommentNSID:
	default:
		return fmt.Errorf("unexpected collection: '%s'", record.Collection())
	}

	comments, err := db.GetComments(s.db, orm.FilterEq("at_uri", record))
	if err != nil {
		return fmt.Errorf("db: %w", err)
	}
	if len(comments) < 1 {
		l.Info("can't found legacy record from db. skipping migration")
		return nil
	}
	comment := comments[0]

	comment.Collection = tangled.FeedCommentNSID

	// only update from DB if comment is deleted
	if comment.Deleted != nil {
		l.Info("skipping pds migration for deleted record")

		return nil
	}

	// fill missing reference CIDs
	if comment.Subject.Cid == "" {
		cid, err := s.getRecordCid(ctx, syntax.ATURI(comment.Subject.Uri))
		if err != nil {
			return fmt.Errorf("pds: getRecordCid for subject.uri: %w", err)
		}
		comment.Subject.Cid = cid.String()
	}
	if comment.ReplyTo != nil && comment.ReplyTo.Cid == "" {
		uri, err := syntax.ParseATURI(comment.ReplyTo.Uri)
		if err != nil {
			return fmt.Errorf("invalid replyTo.uri: %w", err)
		}

		// assume parent comment is already migrated to `sh.tangled.feed.comment`.
		// fail if it isn't ready
		uri = syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", uri.Authority(), tangled.FeedCommentNSID, uri.RecordKey()))

		cid, err := s.guessParentCommentCid(uri, &comment)
		if err != nil {
			return fmt.Errorf("cbor: guessParentCommentCid for replyTo.uri: %w", err)
		}
		comment.ReplyTo.Uri = uri.String()
		comment.ReplyTo.Cid = cid.String()
	}

	// use same rkey for new record
	rkey := record.RecordKey().String()

	if _, err := comatproto.RepoDeleteRecord(ctx, client, &comatproto.RepoDeleteRecord_Input{
		Repo:       did.String(),
		Collection: record.Collection().String(),
		Rkey:       rkey,
	}); err != nil {
		l.Info("Failed to cleanup old record. Proceeding migration...", "err", err)
	}

	// ensure new record is missing in PDS
	if _, err := agnostic.RepoGetRecord(ctx, client, "", tangled.FeedCommentNSID, did.String(), rkey); err == nil {
		l.Info("New comment record already exists")
	} else {
		// insert new record
		if _, err := comatproto.RepoCreateRecord(ctx, client, &comatproto.RepoCreateRecord_Input{
			Repo:       did.String(),
			Collection: tangled.FeedCommentNSID,
			Rkey:       &rkey,
			Record:     &util.LexiconTypeDecoder{Val: comment.AsRecord()},
		}); err != nil {
			return fmt.Errorf("pds: putRecord: %w", err)
		}
	}

	return nil
}

func (s *Migration) getRecordCid(ctx context.Context, uri syntax.ATURI) (syntax.CID, error) {
	ident, err := s.dir.Lookup(ctx, uri.Authority())
	if err != nil {
		return "", err
	}

	xrpcc := xrpc.Client{Host: ident.PDSEndpoint()}
	out, err := agnostic.RepoGetRecord(ctx, &xrpcc, "", uri.Collection().String(), ident.DID.String(), uri.RecordKey().String())
	if err != nil {
		return "", err
	}
	if out.Cid == nil {
		return "", fmt.Errorf("record CID is empty")
	}

	cid, err := syntax.ParseCID(*out.Cid)
	if err != nil {
		return "", err
	}

	return cid, nil
}

func (s *Migration) guessParentCommentCid(uri syntax.ATURI, comment *models.Comment) (syntax.CID, error) {
	parent, err := db.GetComment(s.db, orm.FilterEq("did", uri.Authority()), orm.FilterEq("rkey", uri.RecordKey()))
	if err != nil {
		return "", fmt.Errorf("db: failed to queyr subject comment: %w", err)
	}
	if parent.Deleted != nil {
		// leave cid empty. reply comment won't pass the schema validation.
		return "", nil
	}

	// since parent comment is also migrating, parent comments subject.cid might be empty
	if parent.Subject.Cid == "" {
		parent.Subject.Cid = comment.Subject.Cid
	}

	buf := new(bytes.Buffer)
	if err := parent.AsRecord().MarshalCBOR(buf); err != nil {
		return "", fmt.Errorf("MarshalCBOR: %w", err)
	}
	c, err := cid.NewPrefixV1(cid.DagCBOR, multihash.SHA2_256).Sum(buf.Bytes())
	if err != nil {
		return "", fmt.Errorf("cid: sum: %w", err)
	}
	return syntax.CID(c.String()), nil
}
