package db

import (
	"database/sql"
	"errors"

	"github.com/bluesky-social/indigo/atproto/syntax"
)

type Repo struct {
	Knot      string
	Owner     syntax.DID
	Rkey      syntax.RecordKey
	RepoDid   syntax.DID
	CreatedAt string
}

func (d *DB) UpsertRepo(repo Repo) error {
	var createdAt sql.NullString
	if repo.CreatedAt != "" {
		createdAt = sql.NullString{String: repo.CreatedAt, Valid: true}
	}
	_, err := d.Exec(
		`insert or replace into repos (repo_did, knot, owner, rkey, created_at)
		 values (?, ?, ?, ?, ?)`,
		repo.RepoDid, repo.Knot, repo.Owner, repo.Rkey, createdAt,
	)
	return err
}

func (d *DB) RepoOwners() ([]syntax.DID, error) {
	repos, err := d.AllRepos()
	if err != nil {
		return nil, err
	}
	seen := make(map[syntax.DID]struct{}, len(repos))
	dids := make([]syntax.DID, 0, len(repos))
	for _, r := range repos {
		if r.Owner == "" {
			continue
		}
		if _, ok := seen[r.Owner]; ok {
			continue
		}
		seen[r.Owner] = struct{}{}
		dids = append(dids, r.Owner)
	}
	return dids, nil
}

func (d *DB) Knots() ([]string, error) {
	rows, err := d.Query(`select distinct knot from repos`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var knots []string
	for rows.Next() {
		var knot string
		if err := rows.Scan(&knot); err != nil {
			return nil, err
		}
		knots = append(knots, knot)
	}

	if err := rows.Err(); err != nil {
		return nil, err
	}

	return knots, nil
}

func scanRepo(row interface{ Scan(...any) error }) (*Repo, error) {
	var knot, owner, rkey, repoDid string
	if err := row.Scan(&knot, &owner, &rkey, &repoDid); err != nil {
		return nil, err
	}
	return &Repo{
		Knot:    knot,
		Owner:   syntax.DID(owner),
		Rkey:    syntax.RecordKey(rkey),
		RepoDid: syntax.DID(repoDid),
	}, nil
}

func (d *DB) GetRepoByDid(repoDid syntax.DID) (*Repo, error) {
	return scanRepo(d.QueryRow(
		`select knot, owner, rkey, repo_did from repos where repo_did = ?`,
		repoDid.String(),
	))
}

func (d *DB) AllRepos() ([]Repo, error) {
	rows, err := d.Query(`select knot, owner, rkey, repo_did from repos`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var repos []Repo
	for rows.Next() {
		r, err := scanRepo(rows)
		if err != nil {
			return nil, err
		}
		repos = append(repos, *r)
	}

	if err := rows.Err(); err != nil {
		return nil, err
	}

	return repos, nil
}

func (d *DB) DeleteRepo(repoDid syntax.DID) error {
	_, err := d.Exec(`delete from repos where repo_did = ?`, repoDid)
	return err
}

// DeleteRepoByOwnerRkey deletes a repo by (owner,rkey) pair and returns deleted repos DID.
func (d *DB) DeleteRepoByOwnerRkey(owner syntax.DID, rkey syntax.RecordKey) (syntax.DID, error) {
	var repoDid string
	err := d.QueryRow(
		`delete from repos where owner = ? and rkey = ? returning repo_did`,
		owner.String(), rkey.String(),
	).Scan(&repoDid)
	if errors.Is(err, sql.ErrNoRows) {
		return "", nil
	}
	if err != nil {
		return "", err
	}
	return syntax.DID(repoDid), nil
}
