//
// Created by vfs on 02.05.2024.
//

package main

import "errors"
import "time"
import "golang.org/x/crypto/bcrypt"
import "database/sql"
import _ "github.com/lib/pq"
import "fmt"

import "github.com/stripe/stripe-go/v78"
import "github.com/stripe/stripe-go/v78/customer"

var ErrNoRecord           = errors.New("no matching record found")
var ErrInvalidCredentials = errors.New("invalid credentials")
var ErrDuplicateEmail     = errors.New("duplicate email")
var ErrDuplicateUsername  = errors.New("duplicate username")

type Account struct {
	ID int32
	Username string
	Password []byte
	Color int32
	Firstname string
	Lastname string
	Email string
	Created time.Time
	StripeID string
}

type SubscriptionStatus string

const (
	incomplete         SubscriptionStatus = "incomplete"
	incomplete_expired SubscriptionStatus = "incomplete_expired"
	trialing           SubscriptionStatus = "trialing"
	active             SubscriptionStatus = "active"
	past_due           SubscriptionStatus = "past_due"
	canceled           SubscriptionStatus = "canceled"
	unpaid             SubscriptionStatus = "unpaid"
	paused             SubscriptionStatus = "paused"
)

type Subscription struct {
	ID int32
	AccountID int32
	StripeSubscriptionID string
	StripeCheckoutID string
	Status SubscriptionStatus
}

type Usermodel struct {
	DB *sql.DB
}

type SubscriptionModel struct {
	DB *sql.DB
}

func (m *Usermodel) Insert(username string, password string, firstname string, lastname string, email string) (int32, error) {


	hashedpassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
	stmt := `INSERT INTO accounts (username, password, firstname, lastname, email, created) VALUES ($1, $2, $3, $4, $5, NOW()) RETURNING id`

	var insertid int32

	row := m.DB.QueryRow(stmt, username, string(hashedpassword), firstname, lastname, email)
	if row.Err() != nil {
		fmt.Println(row.Err())
		return 0, row.Err()
	}

	err = row.Scan(&insertid)
	if err != nil {
		fmt.Println(err)
		return 0, err
	}

	params := &stripe.CustomerParams{
		Name: stripe.String(fmt.Sprintf("%s %s", firstname, lastname)),
		Email: stripe.String(email),
	}
	customer, err := customer.New(params)

	stmt = `UPDATE accounts SET stripe_id = $1 WHERE id = $2`

	fmt.Println(customer.ID, insertid)

	_, err = m.DB.Exec(stmt, customer.ID, insertid)
	if err != nil {
		fmt.Println(err)
		return 0, err
	}

	return insertid, nil
}

func (m *Usermodel) Delete(id int32) error {
	account, err := users.GetAccount(id)

	if account.StripeID != "" {
		result, err := customer.Del(account.StripeID, nil)
		if err != nil {
			fmt.Println(err)
		}
		fmt.Println(result)
	}

	stmt := `DELETE FROM accounts WHERE id = $1`

	_, err = m.DB.Exec(stmt, id)
	if err != nil {
		fmt.Println(err)
	}


	return nil
}

func (m *Usermodel) GetAccount(id int32) (Account, error) {
	if id == 0 {
		return Account{}, ErrNoRecord
	}
	stmt := `SELECT id, username, password, color, firstname, lastname, email, created, stripe_id FROM accounts WHERE id = $1`
	row := m.DB.QueryRow(stmt, id)

	var account Account
	err := row.Scan(&account.ID, &account.Username, &account.Password, &account.Color, &account.Firstname, &account.Lastname, &account.Email, &account.Created, &account.StripeID)

	if err == sql.ErrNoRows {
		return Account{}, sql.ErrNoRows
	} else if err != nil {
		return Account{}, err
	}

	return account, nil
}

func (m *Usermodel) Authenticate(username string, password string) (int32, error) {
	var id int32
	var hashedpassword []byte
	row := m.DB.QueryRow("SELECT id, password FROM accounts WHERE username = $1", username)
	err := row.Scan(&id, &hashedpassword)
	if err == sql.ErrNoRows {
		return 0, ErrInvalidCredentials
	}

	err = bcrypt.CompareHashAndPassword(hashedpassword, []byte(password))
	if err == bcrypt.ErrMismatchedHashAndPassword {
		return 0, ErrInvalidCredentials
	} else if err != nil {
		return 0, err
	}

	return id, nil
}

func (m *Usermodel) ExistsAccount(id int32) bool {
	var exists bool
	stmt := `SELECT EXISTS(SELECT 1 FROM accounts WHERE id = $1)`
	row := m.DB.QueryRow(stmt, id)
	if row.Err() != nil {
		fmt.Println(row.Err())
	}
	row.Scan(&exists)

	fmt.Println(exists)

	return exists
}

func (m *SubscriptionModel) Insert(stripeid string, stripesubscriptionid string, stripecheckoutid string, status stripe.SubscriptionStatus) (int32, error) {
	var id int32
	stmt := `SELECT id FROM accounts WHERE stripe_id = $1`

	row := m.DB.QueryRow(stmt, stripeid)
	if row.Err() != nil {
		fmt.Println(row.Err())
		return 0, row.Err()
	}

	err := row.Scan(&id)
	if err != nil {
		fmt.Println(err)
		return 0, err
	}

	stmt = `INSERT INTO subscriptions (account_id, stripe_subscription_id, stripe_checkout_id, status) VALUES ($1, $2, $3, $4::subscription_status) RETURNING id`

	var insertid int32

	row = m.DB.QueryRow(stmt, id, string(stripesubscriptionid), string(stripecheckoutid), string(status))
	if row.Err() != nil {
		fmt.Println(row.Err())
		return 0, row.Err()
	}

	err = row.Scan(&insertid)
	if err != nil {
		fmt.Println(err)
		return 0, err
	}

	return insertid, nil
}

func (m *SubscriptionModel) Delete(id int32) error {
	stmt := `DELETE FROM accounts WHERE id = $1`

	_, err := m.DB.Exec(stmt, id)
	if err != nil {
		fmt.Println(err)
		return err
	}

	return nil
}

func (m *SubscriptionModel) GetSubscription(id int32) (Subscription, error) {
	if id == 0 {
		return Subscription{}, ErrNoRecord
	}

	stmt := `SELECT id, account_id, stripe_subscription_id, stripe_checkout_id, status FROM subscriptions WHERE id = $1`
	row := m.DB.QueryRow(stmt, id)

	var subscription Subscription
	err := row.Scan(&subscription.ID, &subscription.AccountID, &subscription.StripeSubscriptionID, &subscription.StripeCheckoutID, &subscription.Status)

	if err == sql.ErrNoRows {
		return Subscription{}, sql.ErrNoRows
	} else if err != nil {
		return Subscription{}, err
	}

	fmt.Println(subscription.Status)

	return subscription, nil
}

func (m *SubscriptionModel) GetSubscriptionsFromAccount(accountid int32) ([]Subscription, error) {
	if accountid == 0 {
		return nil, ErrNoRecord
	}

	stmt := `SELECT id, account_id, stripe_subscription_id, stripe_checkout_id, status FROM subscriptions WHERE account_id = $1`
	rows, err := m.DB.Query(stmt, accountid)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var subscriptions []Subscription
	for rows.Next() {
		var subscription Subscription
		err := rows.Scan(&subscription.ID, &subscription.AccountID, &subscription.StripeSubscriptionID, &subscription.StripeCheckoutID, &subscription.Status)
		if err == sql.ErrNoRows {
			return nil, sql.ErrNoRows
		} else if err != nil {
			return nil, err
		}

		subscriptions = append(subscriptions, subscription)
	}

	return subscriptions, nil
}

func (m *SubscriptionModel) HasActiveSubscription(accountid int32) bool {
	subscriptions, err := m.GetSubscriptionsFromAccount(accountid)
	if err != nil {
		fmt.Println(err)
		return false
	}

	for _, v := range subscriptions {
		if v.Status == active {
			return true
		} else if v.Status == trialing {
			return true
		}
	}

	return false
}
