package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

// CreateSession authenticates with the PDS and returns a session
func (p *Publisher) CreateSession(handle, password string) (*PDSSession, error) {
	payload := map[string]string{
		"identifier": handle,
		"password":   password,
	}
	body, err := json.Marshal(payload)
	if err != nil {
		return nil, err
	}

	resp, err := p.httpClient.Post(
		p.pdsHost+"/xrpc/com.atproto.server.createSession",
		"application/json",
		bytes.NewReader(body),
	)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		respBody, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("auth failed: %s - %s", resp.Status, string(respBody))
	}

	var session PDSSession
	if err := json.NewDecoder(resp.Body).Decode(&session); err != nil {
		return nil, err
	}

	return &session, nil
}

// CreateAccount creates a new account on the PDS
func (p *Publisher) CreateAccount(handle, email, password, inviteCode string) (*PDSSession, error) {
	payload := map[string]interface{}{
		"handle":   handle,
		"email":    email,
		"password": password,
	}
	if inviteCode != "" {
		payload["inviteCode"] = inviteCode
	}

	body, err := json.Marshal(payload)
	if err != nil {
		return nil, err
	}

	resp, err := p.httpClient.Post(
		p.pdsHost+"/xrpc/com.atproto.server.createAccount",
		"application/json",
		bytes.NewReader(body),
	)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, _ := io.ReadAll(resp.Body)

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("create account failed: %s - %s", resp.Status, string(respBody))
	}

	var session PDSSession
	if err := json.Unmarshal(respBody, &session); err != nil {
		return nil, err
	}

	return &session, nil
}

// CreateInviteCode creates an invite code using PDS admin password (Basic Auth)
func (p *Publisher) CreateInviteCode(adminPassword string, useCount int) (string, error) {
	payload := map[string]interface{}{
		"useCount": useCount,
	}

	body, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}

	req, err := http.NewRequest("POST", p.pdsHost+"/xrpc/com.atproto.server.createInviteCode", bytes.NewReader(body))
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/json")
	req.SetBasicAuth("admin", adminPassword)

	resp, err := p.httpClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	respBody, _ := io.ReadAll(resp.Body)

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("create invite failed: %s - %s", resp.Status, string(respBody))
	}

	var result struct {
		Code string `json:"code"`
	}
	if err := json.Unmarshal(respBody, &result); err != nil {
		return "", err
	}

	return result.Code, nil
}

// FollowAccount creates a follow record from the authenticated session to the target DID
func (p *Publisher) FollowAccount(session *PDSSession, targetDID string) error {
	now := time.Now().UTC().Format(time.RFC3339)
	record := map[string]interface{}{
		"$type":     "app.bsky.graph.follow",
		"subject":   targetDID,
		"createdAt": now,
	}

	payload := map[string]interface{}{
		"repo":       session.DID,
		"collection": "app.bsky.graph.follow",
		"record":     record,
	}

	body, err := json.Marshal(payload)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", p.pdsHost+"/xrpc/com.atproto.repo.createRecord", bytes.NewReader(body))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+session.AccessJwt)

	resp, err := p.httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		respBody, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("follow failed: %s - %s", resp.Status, string(respBody))
	}

	return nil
}

// FollowAsDirectory logs in as the directory account and follows the target DID
func (p *Publisher) FollowAsDirectory(targetDID string) error {
	dirHandle := os.Getenv("DIRECTORY_HANDLE")
	dirPassword := os.Getenv("DIRECTORY_PASSWORD")

	if dirHandle == "" || dirPassword == "" {
		return nil
	}

	session, err := p.CreateSession(dirHandle, dirPassword)
	if err != nil {
		return fmt.Errorf("directory login failed: %w", err)
	}

	return p.FollowAccount(session, targetDID)
}
