package data

import (
	"time"
	"database/sql"
	"errors"
	"context"
	"fmt"
	
	"party.at/party/internal/validator"
)

type Issue struct {
	ID              int64     `json:"id"`
	Title           string    `json:"title"`
	Description     string    `json:"description"`
	StartTime       time.Time `json:"start_time"`
	EndTime         time.Time `json:"end_time"`
	N               []byte    `json:"-"`
    E               int       `json:"-"`
    PrivatePem      []byte    `json:"-"`
	Created         time.Time `json:"created"`
	Version         int32     `json:"version"`
}

func ValidateIssue(v *validator.Validator, issue *Issue) {
	v.Check(issue.Title != "",             "title",       "must be provided")
	v.Check(len(issue.Title) <= 500,       "title",       "must not be more than 500 bytes long")
	v.Check(issue.Description != "",       "description", "must be provided")
	v.Check(len(issue.Description) <= 500, "description", "must not be greater than 500 bytes long")
	// v.Check(issue.StartTime != "",         "start_time",  "must be provided")
	// v.Check(len(issue.StartTime) <= 500,   "start_time",  "must not be more than 500 bytes long")
	// v.Check(issue.EndTime != "",           "end_time",    "must be provided")
	// v.Check(len(issue.EndTime) <= 500,     "end_time",    "must not be more than 500 bytes long")
}

type IssueModel struct {
	DB *sql.DB
}

func (m IssueModel) Insert(issue *Issue) error {
	query :=
`INSERT INTO issues (title, description, start_time, end_time, rsa_n, rsa_e, rsa_private_pem)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, created, version`

	args := []interface{}{
		issue.Title,
		issue.Description,
		issue.StartTime,
		issue.EndTime,
		issue.N,
		issue.E,
		issue.PrivatePem,
	}

	return m.DB.QueryRow(query, args...).Scan(
		&issue.ID,
		&issue.Created,
		&issue.Version,
	)
}

func (m IssueModel) InsertWithOptions(issue *Issue, options []*Option) error {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	tx, err := m.DB.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer tx.Rollback()

	query :=
`INSERT INTO issues (title, description, start_time, end_time, rsa_n, rsa_e, rsa_private_pem)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, created, version`

	args := []any{
		issue.Title,
		issue.Description,
		issue.StartTime,
		issue.EndTime,
		issue.N,
		issue.E,
		issue.PrivatePem,
	}

	err = tx.QueryRowContext(ctx, query, args...).Scan(&issue.ID, &issue.Created, &issue.Version)
	if err != nil {
		return err
	}

	for _, option := range options {
		option.IssueID = issue.ID
		err = tx.QueryRowContext(ctx,
`INSERT INTO options (issue_id, label)
VALUES ($1, $2)
RETURNING id, created, version`,
			option.IssueID,
			option.Label,
		).Scan(&option.ID, &option.Created, &option.Version)
		if err != nil {
			return err
		}
	}

	err = tx.Commit()
	if err != nil {
		return err
	}
	return nil
}

func (m IssueModel) Get(id int64) (*Issue, error) {
	if id < 1 {
		return nil, ErrRecordNotFound
	}

	query :=
`SELECT id, title, description, start_time, end_time, rsa_n, rsa_e, rsa_private_pem, created, version
FROM issues
WHERE id = $1`

	var issue Issue

	err := m.DB.QueryRow(query, id).Scan(
		&issue.ID,
		&issue.Title,
		&issue.Description,
		&issue.StartTime,
		&issue.EndTime,
		&issue.N,
		&issue.E,
		&issue.PrivatePem,
		&issue.Created,
		&issue.Version,
	)

	if err != nil {
		switch {
		case errors.Is(err, sql.ErrNoRows):
			return nil, ErrRecordNotFound
		default:
			return nil, err
		}
	}

	return &issue, nil
}

func (m IssueModel) Update(issue *Issue) error {
	query := `
	UPDATE issues
	SET
		title = $1,
		description = $2,
		start_time = $3,
		end_time = $4,
		rsa_n = $5,
		rsa_e = $6,
		rsa_private_pem = $7,
		version = version + 1
	WHERE id = $8 AND version = $9
	RETURNING version`

	args := []interface{}{
		issue.Title,
		issue.Description,
		issue.StartTime,
		issue.EndTime,
		issue.N,
		issue.E,
		issue.PrivatePem,
		issue.ID,
		issue.Version,
	}

	err := m.DB.QueryRow(query, args...).Scan(&issue.Version)
	if err != nil {
		switch {
		case errors.Is(err, sql.ErrNoRows):
			return ErrEditConflict
		default:
			return err
		}
	}

	return nil
}

func (m IssueModel) Delete(id int64) error {
	if id < 1 {
		return ErrRecordNotFound
	}

	query := `
	DELETE FROM issues
	WHERE id = $1`

	result, err := m.DB.Exec(query, id)
	if err != nil {
		return err
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return err
	}

	if rowsAffected == 0 {
		return ErrRecordNotFound
	}

	return nil
}

func (m IssueModel) GetAll(title string, filters Filters) ([]*Issue, Metadata, error) {
	query := fmt.Sprintf(`
	SELECT
		COUNT(*) OVER(), id, title, description, start_time, end_time, rsa_n, rsa_e, rsa_private_pem, created, version
	FROM
		issues
	WHERE
		(to_tsvector('simple', title) @@ plainto_tsquery('simple', $1) OR $1 = '')
	ORDER BY
		%s %s, id ASC
	LIMIT
		$2
	OFFSET
		$3`,
		filters.sortColumn(),
		filters.sortDirection(),
	)

	ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
	defer cancel()

	args := []interface{}{title, filters.limit(), filters.offset()}

	rows, err := m.DB.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, Metadata{}, err
	}

	defer rows.Close()

	totalRecords := 0
	issues := []*Issue{}

	for rows.Next() {
		var issue Issue

		err := rows.Scan(
			&totalRecords,
			&issue.ID,
			&issue.Title,
			&issue.Description,
			&issue.StartTime,
			&issue.EndTime,
			&issue.N,
			&issue.E,
			&issue.PrivatePem,
			&issue.Created,
			&issue.Version,
		)

		if err != nil {
			return nil, Metadata{}, err
		}

		issues = append(issues, &issue)
	}

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

	metadata := calculateMetadata(totalRecords, filters.Page, filters.PageSize)

	return issues, metadata, nil
}
