package resolver

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/dennis/fediresolve/formatter"
	"github.com/tidwall/gjson"
)

// Resolver handles the resolution of Fediverse URLs and handles
type Resolver struct {
	client *http.Client
}

// NewResolver creates a new Resolver instance
func NewResolver() *Resolver {
	return &Resolver{
		client: &http.Client{
			Timeout: 30 * time.Second,
		},
	}
}

// ResolveInput is a convenience function that creates a new resolver and resolves the input
func ResolveInput(input string) (string, error) {
	r := NewResolver()
	return r.Resolve(input)
}

// Resolve takes a URL or handle and resolves it to a formatted result
func (r *Resolver) Resolve(input string) (string, error) {
	// Check if input looks like a Fediverse handle (@username@domain.tld)
	if strings.Contains(input, "@") {
		// Simple check for handle format
		if strings.HasPrefix(input, "@") || strings.Count(input, "@") == 2 {
			fmt.Println("Detected Fediverse handle, using WebFinger resolution")
			return r.resolveHandle(input)
		}
	}

	// Otherwise treat as URL
	fmt.Println("Detected URL, attempting direct resolution")
	return r.resolveURL(input)
}

// WebFingerResponse represents the structure of a WebFinger response
type WebFingerResponse struct {
	Subject string `json:"subject"`
	Links   []struct {
		Rel  string `json:"rel"`
		Type string `json:"type"`
		Href string `json:"href"`
	} `json:"links"`
}

// resolveHandle resolves a Fediverse handle using WebFinger
func (r *Resolver) resolveHandle(handle string) (string, error) {
	// Remove @ prefix if present
	if handle[0] == '@' {
		handle = handle[1:]
	}

	// Split handle into username and domain
	parts := strings.Split(handle, "@")
	if len(parts) != 2 {
		return "", fmt.Errorf("invalid handle format: %s", handle)
	}

	username, domain := parts[0], parts[1]
	
	// Construct WebFinger URL with proper URL encoding
	resource := fmt.Sprintf("acct:%s@%s", username, domain)
	webfingerURL := fmt.Sprintf("https://%s/.well-known/webfinger?resource=%s", 
		domain, url.QueryEscape(resource))

	fmt.Printf("Fetching WebFinger data from: %s\n", webfingerURL)
	
	// Create request for WebFinger data
	req, err := http.NewRequest("GET", webfingerURL, nil)
	if err != nil {
		return "", fmt.Errorf("error creating WebFinger request: %v", err)
	}
	
	// Set appropriate headers for WebFinger
	req.Header.Set("Accept", "application/jrd+json, application/json")
	req.Header.Set("User-Agent", "FediResolve/1.0 (https://github.com/dennis/fediresolve)")
	
	// Fetch WebFinger data
	resp, err := r.client.Do(req)
	if err != nil {
		return "", fmt.Errorf("error fetching WebFinger data: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("WebFinger request failed with status: %s", resp.Status)
	}

	// Read and parse the WebFinger response
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("error reading WebFinger response: %v", err)
	}
	
	fmt.Printf("WebFinger response content type: %s\n", resp.Header.Get("Content-Type"))
	fmt.Printf("WebFinger response body: %s\n", string(body))
	
	var webfinger WebFingerResponse
	if err := json.Unmarshal(body, &webfinger); err != nil {
		return "", fmt.Errorf("error decoding WebFinger response: %v", err)
	}

	// Find the ActivityPub actor URL
	var actorURL string

	// First try to find a link with rel="self" and type containing "activity+json"
	for _, link := range webfinger.Links {
		if link.Rel == "self" && strings.Contains(link.Type, "activity+json") {
			actorURL = link.Href
			fmt.Printf("Found ActivityPub actor URL with type %s: %s\n", link.Type, actorURL)
			break
		}
	}

	// If not found, try with rel="self" and any type
	if actorURL == "" {
		for _, link := range webfinger.Links {
			if link.Rel == "self" {
				actorURL = link.Href
				fmt.Printf("Found ActivityPub actor URL with rel=self: %s\n", actorURL)
				break
			}
		}
	}

	// If still not found, try with any link that might be useful
	if actorURL == "" {
		for _, link := range webfinger.Links {
			if link.Rel == "http://webfinger.net/rel/profile-page" {
				actorURL = link.Href
				fmt.Printf("Using profile page as fallback: %s\n", actorURL)
				break
			}
		}
	}

	if actorURL == "" {
		return "", fmt.Errorf("could not find any suitable URL in WebFinger response")
	}

	// Now fetch the actor data
	return r.fetchActivityPubObject(actorURL)
}

// resolveURL resolves a Fediverse URL to its ActivityPub representation
func (r *Resolver) resolveURL(inputURL string) (string, error) {
	// Parse the URL
	parsedURL, err := url.Parse(inputURL)
	if err != nil {
		return "", fmt.Errorf("invalid URL: %v", err)
	}

	// Ensure the URL has a scheme
	if parsedURL.Scheme == "" {
		inputURL = "https://" + inputURL
		parsedURL, err = url.Parse(inputURL)
		if err != nil {
			return "", fmt.Errorf("invalid URL: %v", err)
		}
	}

	// Try to fetch the ActivityPub object directly
	return r.fetchActivityPubObject(inputURL)
}

// fetchActivityPubObject fetches an ActivityPub object from a URL
func (r *Resolver) fetchActivityPubObject(objectURL string) (string, error) {
	fmt.Printf("Fetching ActivityPub object from: %s\n", objectURL)
	
	// Make sure the URL is valid
	parsedURL, err := url.Parse(objectURL)
	if err != nil {
		return "", fmt.Errorf("invalid URL: %v", err)
	}

	// Ensure the URL has a scheme
	if parsedURL.Scheme == "" {
		objectURL = "https://" + objectURL
		parsedURL, err = url.Parse(objectURL)
		if err != nil {
			return "", fmt.Errorf("invalid URL: %v", err)
		}
	}
	
	// Create the request
	req, err := http.NewRequest("GET", objectURL, nil)
	if err != nil {
		return "", fmt.Errorf("error creating request: %v", err)
	}

	// Set Accept headers to request ActivityPub data
	// Use multiple Accept headers to increase compatibility with different servers
	req.Header.Set("Accept", "application/activity+json, application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\", application/json")
	req.Header.Set("User-Agent", "FediResolve/1.0 (https://github.com/dennis/fediresolve)")

	// Perform the request
	fmt.Printf("Sending request with headers: %v\n", req.Header)
	resp, err := r.client.Do(req)
	if err != nil {
		return "", fmt.Errorf("error fetching ActivityPub data: %v", err)
	}
	defer resp.Body.Close()

	fmt.Printf("Received response with status: %s\n", resp.Status)
	if resp.StatusCode != http.StatusOK {
		// Try to read the error response body for debugging
		errorBody, _ := ioutil.ReadAll(resp.Body)
		return "", fmt.Errorf("ActivityPub request failed with status: %s\nResponse body: %s", 
			resp.Status, string(errorBody))
	}

	// Read and parse the ActivityPub response
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("error reading ActivityPub response: %v", err)
	}
	
	// Debug output
	fmt.Printf("ActivityPub response content type: %s\n", resp.Header.Get("Content-Type"))
	
	// Check if the response is empty
	if len(body) == 0 {
		return "", fmt.Errorf("received empty response body")
	}
	
	// Try to decode the JSON response
	var data map[string]interface{}
	if err := json.Unmarshal(body, &data); err != nil {
		// If we can't parse as JSON, return the raw response for debugging
		return "", fmt.Errorf("error decoding ActivityPub response: %v\nResponse body: %s", 
			err, string(body))
	}

	// Check if this is a shared/forwarded object and we need to fetch the original
	jsonData, _ := json.Marshal(data)
	jsonStr := string(jsonData)

	// Check for various ActivityPub types that might reference an original object
	if gjson.Get(jsonStr, "type").String() == "Announce" {
		// This is a boost/share, get the original object
		originalURL := gjson.Get(jsonStr, "object").String()
		if originalURL != "" && (strings.HasPrefix(originalURL, "http://") || strings.HasPrefix(originalURL, "https://")) {
			fmt.Printf("Found Announce, following original at: %s\n", originalURL)
			return r.fetchActivityPubObject(originalURL)
		}
	}

	// Format the result
	return formatter.Format(data)
}
